[Rt-commit] rt branch, 4.2/cf-searching, created. rt-4.1.8-189-gcb1a739

Alex Vandiver alexmv at bestpractical.com
Fri May 10 03:13:14 EDT 2013


The branch, 4.2/cf-searching has been created
        at  cb1a739cfb79b125a441a6dbe2a862049218b6d3 (commit)

- Log -----------------------------------------------------------------
commit 890d47ef1013760ae225ce8190e0528cc1543871
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:10:13 2013 -0500

    Move custom field search code into RT::SearchBuilder
    
    This merely moves two functions as-is, in preparation for refactoring
    them to be useful on non-Ticket classes.  As such, the code is placed in
    _LimitCustomField, and the existing LimitCustomField implementation is
    unchanged -- _LimitCustomField is still Ticket-specific.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index cd1c089..bc47576 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -202,6 +202,105 @@ sub _SingularClass {
     return $class;
 }
 
+=head2 _CustomFieldJoin
+
+Factor out the Join of custom fields so we can use it for sorting too
+
+=cut
+
+sub _CustomFieldJoin {
+    my ($self, $cfkey, $cfid, $field) = @_;
+    # Perform one Join per CustomField
+    if ( $self->{_sql_object_cfv_alias}{$cfkey} ||
+         $self->{_sql_cf_alias}{$cfkey} )
+    {
+        return ( $self->{_sql_object_cfv_alias}{$cfkey},
+                 $self->{_sql_cf_alias}{$cfkey} );
+    }
+
+    my ($TicketCFs, $CFs);
+    if ( $cfid ) {
+        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+            TYPE   => 'LEFT',
+            ALIAS1 => 'main',
+            FIELD1 => 'id',
+            TABLE2 => 'ObjectCustomFieldValues',
+            FIELD2 => 'ObjectId',
+        );
+        $self->Limit(
+            LEFTJOIN        => $TicketCFs,
+            FIELD           => 'CustomField',
+            VALUE           => $cfid,
+            ENTRYAGGREGATOR => 'AND'
+        );
+    }
+    else {
+        my $ocfalias = $self->Join(
+            TYPE       => 'LEFT',
+            FIELD1     => 'Queue',
+            TABLE2     => 'ObjectCustomFields',
+            FIELD2     => 'ObjectId',
+        );
+
+        $self->Limit(
+            LEFTJOIN        => $ocfalias,
+            ENTRYAGGREGATOR => 'OR',
+            FIELD           => 'ObjectId',
+            VALUE           => '0',
+        );
+
+        $CFs = $self->{_sql_cf_alias}{$cfkey} = $self->Join(
+            TYPE       => 'LEFT',
+            ALIAS1     => $ocfalias,
+            FIELD1     => 'CustomField',
+            TABLE2     => 'CustomFields',
+            FIELD2     => 'id',
+        );
+        $self->Limit(
+            LEFTJOIN        => $CFs,
+            ENTRYAGGREGATOR => 'AND',
+            FIELD           => 'LookupType',
+            VALUE           => 'RT::Queue-RT::Ticket',
+        );
+        $self->Limit(
+            LEFTJOIN        => $CFs,
+            ENTRYAGGREGATOR => 'AND',
+            FIELD           => 'Name',
+            VALUE           => $field,
+        );
+
+        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+            TYPE   => 'LEFT',
+            ALIAS1 => $CFs,
+            FIELD1 => 'id',
+            TABLE2 => 'ObjectCustomFieldValues',
+            FIELD2 => 'CustomField',
+        );
+        $self->Limit(
+            LEFTJOIN        => $TicketCFs,
+            FIELD           => 'ObjectId',
+            VALUE           => 'main.id',
+            QUOTEVALUE      => 0,
+            ENTRYAGGREGATOR => 'AND',
+        );
+    }
+    $self->Limit(
+        LEFTJOIN        => $TicketCFs,
+        FIELD           => 'ObjectType',
+        VALUE           => 'RT::Ticket',
+        ENTRYAGGREGATOR => 'AND'
+    );
+    $self->Limit(
+        LEFTJOIN        => $TicketCFs,
+        FIELD           => 'Disabled',
+        OPERATOR        => '=',
+        VALUE           => '0',
+        ENTRYAGGREGATOR => 'AND'
+    );
+
+    return ($TicketCFs, $CFs);
+}
+
 sub LimitCustomField {
     my $self = shift;
     my %args = ( VALUE        => undef,
@@ -242,6 +341,380 @@ sub LimitCustomField {
     );
 }
 
+use Regexp::Common qw(RE_net_IPv4);
+use Regexp::Common::net::CIDR;
+
+sub _LimitCustomField {
+    my $self = shift;
+    my ( $field, $queue, $cfid, $cf, $column, $op, $value, %rest ) = @_;
+
+
+# If we're trying to find custom fields that don't match something, we
+# want tickets where the custom field has no value at all.  Note that
+# we explicitly don't include the "IS NULL" case, since we would
+# otherwise end up with a redundant clause.
+
+    my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
+    my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
+
+    my $fix_op = sub {
+        return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
+
+        my %args = @_;
+        return %args unless $args{'FIELD'} eq 'LargeContent';
+        
+        my $op = $args{'OPERATOR'};
+        if ( $op eq '=' ) {
+            $args{'OPERATOR'} = 'MATCHES';
+        }
+        elsif ( $op eq '!=' ) {
+            $args{'OPERATOR'} = 'NOT MATCHES';
+        }
+        elsif ( $op =~ /^[<>]=?$/ ) {
+            $args{'FUNCTION'} = "TO_CHAR( $args{'ALIAS'}.LargeContent )";
+        }
+        return %args;
+    };
+
+    if ( $cf && $cf->Type eq 'IPAddress' ) {
+        my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
+        if ($parsed) {
+            $value = $parsed;
+        }
+        else {
+            $RT::Logger->warn("$value is not a valid IPAddress");
+        }
+    }
+
+    if ( $cf && $cf->Type eq 'IPAddressRange' ) {
+
+        if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
+
+            # convert incomplete 192.168/24 to 192.168.0.0/24 format
+            $value =
+              join( '.', map $_ || 0, ( split /\./, $1 )[ 0 .. 3 ] ) . "/$2"
+              || $value;
+        }
+
+        my ( $start_ip, $end_ip ) =
+          RT::ObjectCustomFieldValue->ParseIPRange($value);
+        if ( $start_ip && $end_ip ) {
+            if ( $op =~ /^([<>])=?$/ ) {
+                my $is_less = $1 eq '<' ? 1 : 0;
+                if ( $is_less ) {
+                    $value = $start_ip;
+                }
+                else {
+                    $value = $end_ip;
+                }
+            }
+            else {
+                $value = join '-', $start_ip, $end_ip;
+            }
+        }
+        else {
+            $RT::Logger->warn("$value is not a valid IPAddressRange");
+        }
+    }
+
+    if ( $cf && $cf->Type =~ /^Date(?:Time)?$/ ) {
+        my $date = RT::Date->new( $self->CurrentUser );
+        $date->Set( Format => 'unknown', Value => $value );
+        if ( $date->Unix ) {
+
+            if (
+                   $cf->Type eq 'Date'
+                || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i
+                || (   $value !~ /midnight|\d+:\d+:\d+/i
+                    && $date->Time( Timezone => 'user' ) eq '00:00:00' )
+              )
+            {
+                $value = $date->Date( Timezone => 'user' );
+            }
+            else {
+                $value = $date->DateTime;
+            }
+        }
+        else {
+            $RT::Logger->warn("$value is not a valid date string");
+        }
+    }
+
+    my $single_value = !$cf || !$cfid || $cf->SingleValue;
+
+    my $cfkey = $cfid ? $cfid : "$queue.$field";
+
+    if ( $null_op && !$column ) {
+        # IS[ NOT] NULL without column is the same as has[ no] any CF value,
+        # we can reuse our default joins for this operation
+        # with column specified we have different situation
+        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        $self->_OpenParen;
+        $self->Limit(
+            ALIAS    => $TicketCFs,
+            FIELD    => 'id',
+            OPERATOR => $op,
+            VALUE    => $value,
+            %rest
+        );
+        $self->Limit(
+            ALIAS      => $CFs,
+            FIELD      => 'Name',
+            OPERATOR   => 'IS NOT',
+            VALUE      => 'NULL',
+            QUOTEVALUE => 0,
+            ENTRYAGGREGATOR => 'AND',
+        ) if $CFs;
+        $self->_CloseParen;
+    }
+    elsif ( $op !~ /^[<>]=?$/ && (  $cf && $cf->Type eq 'IPAddressRange')) {
+    
+        my ($start_ip, $end_ip) = split /-/, $value;
+        
+        $self->_OpenParen;
+        if ( $op !~ /NOT|!=|<>/i ) { # positive equation
+            $self->_LimitCustomField(
+                $field, $queue, $cfid, $cf, 'Content', '<=', $end_ip, %rest,
+            );
+            $self->_LimitCustomField(
+                $field, $queue, $cfid, $cf, 'LargeContent', '>=', $start_ip, %rest,
+                ENTRYAGGREGATOR => 'AND',
+            ); 
+            # as well limit borders so DB optimizers can use better
+            # estimations and scan less rows
+# have to disable this tweak because of ipv6
+#            $self->_CustomFieldLimit(
+#                $field, '>=', '000.000.000.000', %rest,
+#                SUBKEY          => $rest{'SUBKEY'}. '.Content',
+#                ENTRYAGGREGATOR => 'AND',
+#            );
+#            $self->_CustomFieldLimit(
+#                $field, '<=', '255.255.255.255', %rest,
+#                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
+#                ENTRYAGGREGATOR => 'AND',
+#            );  
+        }       
+        else { # negative equation
+            $self->_LimitCustomField( $field, $queue, $cfid, $cf, 'Content', '>', $end_ip, %rest);
+            $self->_LimitCustomField(
+                $field, $queue, $cfid, $cf, 'LargeContent', '<', $start_ip, %rest,
+                ENTRYAGGREGATOR => 'OR',
+            );  
+            # TODO: as well limit borders so DB optimizers can use better
+            # estimations and scan less rows, but it's harder to do
+            # as we have OR aggregator
+        }
+        $self->_CloseParen;
+    } 
+    elsif ( !$negative_op || $single_value ) {
+        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
+        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+
+        $self->_OpenParen;
+
+        $self->_OpenParen;
+
+        $self->_OpenParen;
+        # if column is defined then deal only with it
+        # otherwise search in Content and in LargeContent
+        if ( $column ) {
+            $self->Limit( $fix_op->(
+                ALIAS      => $TicketCFs,
+                FIELD      => $column,
+                OPERATOR   => $op,
+                VALUE      => $value,
+                CASESENSITIVE => 0,
+                %rest
+            ) );
+            $self->_CloseParen;
+            $self->_CloseParen;
+            $self->_CloseParen;
+        }
+        else {
+            # need special treatment for Date
+            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
+                # no time specified, that means we want everything on a
+                # particular day.  in the database, we need to check for >
+                # and < the edges of that day.
+                my $date = RT::Date->new( $self->CurrentUser );
+                $date->Set( Format => 'unknown', Value => $value );
+                my $daystart = $date->ISO;
+                $date->AddDay;
+                my $dayend = $date->ISO;
+
+                $self->_OpenParen;
+
+                $self->Limit(
+                    ALIAS    => $TicketCFs,
+                    FIELD    => 'Content',
+                    OPERATOR => ">=",
+                    VALUE    => $daystart,
+                    %rest,
+                );
+
+                $self->Limit(
+                    ALIAS    => $TicketCFs,
+                    FIELD    => 'Content',
+                    OPERATOR => "<",
+                    VALUE    => $dayend,
+                    %rest,
+                    ENTRYAGGREGATOR => 'AND',
+                );
+
+                $self->_CloseParen;
+            }
+            elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
+                if ( length( Encode::encode_utf8($value) ) < 256 ) {
+                    $self->Limit(
+                        ALIAS    => $TicketCFs,
+                        FIELD    => 'Content',
+                        OPERATOR => $op,
+                        VALUE    => $value,
+                        CASESENSITIVE => 0,
+                        %rest
+                    );
+                }
+                else {
+                    $self->_OpenParen;
+                    $self->Limit(
+                        ALIAS           => $TicketCFs,
+                        FIELD           => 'Content',
+                        OPERATOR        => '=',
+                        VALUE           => '',
+                        ENTRYAGGREGATOR => 'OR'
+                    );
+                    $self->Limit(
+                        ALIAS           => $TicketCFs,
+                        FIELD           => 'Content',
+                        OPERATOR        => 'IS',
+                        VALUE           => 'NULL',
+                        ENTRYAGGREGATOR => 'OR'
+                    );
+                    $self->_CloseParen;
+                    $self->Limit( $fix_op->(
+                        ALIAS           => $TicketCFs,
+                        FIELD           => 'LargeContent',
+                        OPERATOR        => $op,
+                        VALUE           => $value,
+                        ENTRYAGGREGATOR => 'AND',
+                        CASESENSITIVE => 0,
+                    ) );
+                }
+            }
+            else {
+                $self->Limit(
+                    ALIAS    => $TicketCFs,
+                    FIELD    => 'Content',
+                    OPERATOR => $op,
+                    VALUE    => $value,
+                    CASESENSITIVE => 0,
+                    %rest
+                );
+
+                $self->_OpenParen;
+                $self->_OpenParen;
+                $self->Limit(
+                    ALIAS           => $TicketCFs,
+                    FIELD           => 'Content',
+                    OPERATOR        => '=',
+                    VALUE           => '',
+                    ENTRYAGGREGATOR => 'OR'
+                );
+                $self->Limit(
+                    ALIAS           => $TicketCFs,
+                    FIELD           => 'Content',
+                    OPERATOR        => 'IS',
+                    VALUE           => 'NULL',
+                    ENTRYAGGREGATOR => 'OR'
+                );
+                $self->_CloseParen;
+                $self->Limit( $fix_op->(
+                    ALIAS           => $TicketCFs,
+                    FIELD           => 'LargeContent',
+                    OPERATOR        => $op,
+                    VALUE           => $value,
+                    ENTRYAGGREGATOR => 'AND',
+                    CASESENSITIVE => 0,
+                ) );
+                $self->_CloseParen;
+            }
+            $self->_CloseParen;
+
+            # XXX: if we join via CustomFields table then
+            # because of order of left joins we get NULLs in
+            # CF table and then get nulls for those records
+            # in OCFVs table what result in wrong results
+            # as decifer method now tries to load a CF then
+            # we fall into this situation only when there
+            # are more than one CF with the name in the DB.
+            # the same thing applies to order by call.
+            # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
+            # we want treat IS NULL as (not applies or has
+            # no value)
+            $self->Limit(
+                ALIAS           => $CFs,
+                FIELD           => 'Name',
+                OPERATOR        => 'IS NOT',
+                VALUE           => 'NULL',
+                QUOTEVALUE      => 0,
+                ENTRYAGGREGATOR => 'AND',
+            ) if $CFs;
+            $self->_CloseParen;
+
+            if ($negative_op) {
+                $self->Limit(
+                    ALIAS           => $TicketCFs,
+                    FIELD           => $column || 'Content',
+                    OPERATOR        => 'IS',
+                    VALUE           => 'NULL',
+                    QUOTEVALUE      => 0,
+                    ENTRYAGGREGATOR => 'OR',
+                );
+            }
+
+            $self->_CloseParen;
+        }
+    }
+    else {
+        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
+        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+
+        # reverse operation
+        $op =~ s/!|NOT\s+//i;
+
+        # if column is defined then deal only with it
+        # otherwise search in Content and in LargeContent
+        if ( $column ) {
+            $self->Limit( $fix_op->(
+                LEFTJOIN   => $TicketCFs,
+                ALIAS      => $TicketCFs,
+                FIELD      => $column,
+                OPERATOR   => $op,
+                VALUE      => $value,
+                CASESENSITIVE => 0,
+            ) );
+        }
+        else {
+            $self->Limit(
+                LEFTJOIN   => $TicketCFs,
+                ALIAS      => $TicketCFs,
+                FIELD      => 'Content',
+                OPERATOR   => $op,
+                VALUE      => $value,
+                CASESENSITIVE => 0,
+            );
+        }
+        $self->Limit(
+            %rest,
+            ALIAS      => $TicketCFs,
+            FIELD      => 'id',
+            OPERATOR   => 'IS',
+            VALUE      => 'NULL',
+            QUOTEVALUE => 0,
+        );
+    }
+}
+
 =head2 Limit PARAMHASH
 
 This Limit sub calls SUPER::Limit, but defaults "CASESENSITIVE" to 1, thus
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 6437d78..26b84bf 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1083,105 +1083,6 @@ sub _CustomFieldDecipher {
     return ($queue, $field, $cf, $column);
 }
 
-=head2 _CustomFieldJoin
-
-Factor out the Join of custom fields so we can use it for sorting too
-
-=cut
-
-sub _CustomFieldJoin {
-    my ($self, $cfkey, $cfid, $field) = @_;
-    # Perform one Join per CustomField
-    if ( $self->{_sql_object_cfv_alias}{$cfkey} ||
-         $self->{_sql_cf_alias}{$cfkey} )
-    {
-        return ( $self->{_sql_object_cfv_alias}{$cfkey},
-                 $self->{_sql_cf_alias}{$cfkey} );
-    }
-
-    my ($TicketCFs, $CFs);
-    if ( $cfid ) {
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
-            TYPE   => 'LEFT',
-            ALIAS1 => 'main',
-            FIELD1 => 'id',
-            TABLE2 => 'ObjectCustomFieldValues',
-            FIELD2 => 'ObjectId',
-        );
-        $self->Limit(
-            LEFTJOIN        => $TicketCFs,
-            FIELD           => 'CustomField',
-            VALUE           => $cfid,
-            ENTRYAGGREGATOR => 'AND'
-        );
-    }
-    else {
-        my $ocfalias = $self->Join(
-            TYPE       => 'LEFT',
-            FIELD1     => 'Queue',
-            TABLE2     => 'ObjectCustomFields',
-            FIELD2     => 'ObjectId',
-        );
-
-        $self->Limit(
-            LEFTJOIN        => $ocfalias,
-            ENTRYAGGREGATOR => 'OR',
-            FIELD           => 'ObjectId',
-            VALUE           => '0',
-        );
-
-        $CFs = $self->{_sql_cf_alias}{$cfkey} = $self->Join(
-            TYPE       => 'LEFT',
-            ALIAS1     => $ocfalias,
-            FIELD1     => 'CustomField',
-            TABLE2     => 'CustomFields',
-            FIELD2     => 'id',
-        );
-        $self->Limit(
-            LEFTJOIN        => $CFs,
-            ENTRYAGGREGATOR => 'AND',
-            FIELD           => 'LookupType',
-            VALUE           => 'RT::Queue-RT::Ticket',
-        );
-        $self->Limit(
-            LEFTJOIN        => $CFs,
-            ENTRYAGGREGATOR => 'AND',
-            FIELD           => 'Name',
-            VALUE           => $field,
-        );
-
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
-            TYPE   => 'LEFT',
-            ALIAS1 => $CFs,
-            FIELD1 => 'id',
-            TABLE2 => 'ObjectCustomFieldValues',
-            FIELD2 => 'CustomField',
-        );
-        $self->Limit(
-            LEFTJOIN        => $TicketCFs,
-            FIELD           => 'ObjectId',
-            VALUE           => 'main.id',
-            QUOTEVALUE      => 0,
-            ENTRYAGGREGATOR => 'AND',
-        );
-    }
-    $self->Limit(
-        LEFTJOIN        => $TicketCFs,
-        FIELD           => 'ObjectType',
-        VALUE           => 'RT::Ticket',
-        ENTRYAGGREGATOR => 'AND'
-    );
-    $self->Limit(
-        LEFTJOIN        => $TicketCFs,
-        FIELD           => 'Disabled',
-        OPERATOR        => '=',
-        VALUE           => '0',
-        ENTRYAGGREGATOR => 'AND'
-    );
-
-    return ($TicketCFs, $CFs);
-}
-
 =head2 _CustomFieldLimit
 
 Limit based on CustomFields
@@ -1191,10 +1092,6 @@ Meta Data:
 
 =cut
 
-use Regexp::Common qw(RE_net_IPv4);
-use Regexp::Common::net::CIDR;
-
-
 sub _CustomFieldLimit {
     my ( $self, $_field, $op, $value, %rest ) = @_;
 
@@ -1206,373 +1103,7 @@ sub _CustomFieldLimit {
     ($queue, $field, $cf, $column) = $self->_CustomFieldDecipher( $field );
     $cfid = $cf ? $cf->id  : 0 ;
 
-# If we're trying to find custom fields that don't match something, we
-# want tickets where the custom field has no value at all.  Note that
-# we explicitly don't include the "IS NULL" case, since we would
-# otherwise end up with a redundant clause.
-
-    my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
-    my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
-
-    my $fix_op = sub {
-        return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
-
-        my %args = @_;
-        return %args unless $args{'FIELD'} eq 'LargeContent';
-        
-        my $op = $args{'OPERATOR'};
-        if ( $op eq '=' ) {
-            $args{'OPERATOR'} = 'MATCHES';
-        }
-        elsif ( $op eq '!=' ) {
-            $args{'OPERATOR'} = 'NOT MATCHES';
-        }
-        elsif ( $op =~ /^[<>]=?$/ ) {
-            $args{'FUNCTION'} = "TO_CHAR( $args{'ALIAS'}.LargeContent )";
-        }
-        return %args;
-    };
-
-    if ( $cf && $cf->Type eq 'IPAddress' ) {
-        my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
-        if ($parsed) {
-            $value = $parsed;
-        }
-        else {
-            $RT::Logger->warn("$value is not a valid IPAddress");
-        }
-    }
-
-    if ( $cf && $cf->Type eq 'IPAddressRange' ) {
-
-        if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
-
-            # convert incomplete 192.168/24 to 192.168.0.0/24 format
-            $value =
-              join( '.', map $_ || 0, ( split /\./, $1 )[ 0 .. 3 ] ) . "/$2"
-              || $value;
-        }
-
-        my ( $start_ip, $end_ip ) =
-          RT::ObjectCustomFieldValue->ParseIPRange($value);
-        if ( $start_ip && $end_ip ) {
-            if ( $op =~ /^([<>])=?$/ ) {
-                my $is_less = $1 eq '<' ? 1 : 0;
-                if ( $is_less ) {
-                    $value = $start_ip;
-                }
-                else {
-                    $value = $end_ip;
-                }
-            }
-            else {
-                $value = join '-', $start_ip, $end_ip;
-            }
-        }
-        else {
-            $RT::Logger->warn("$value is not a valid IPAddressRange");
-        }
-    }
-
-    if ( $cf && $cf->Type =~ /^Date(?:Time)?$/ ) {
-        my $date = RT::Date->new( $self->CurrentUser );
-        $date->Set( Format => 'unknown', Value => $value );
-        if ( $date->Unix ) {
-
-            if (
-                   $cf->Type eq 'Date'
-                || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i
-                || (   $value !~ /midnight|\d+:\d+:\d+/i
-                    && $date->Time( Timezone => 'user' ) eq '00:00:00' )
-              )
-            {
-                $value = $date->Date( Timezone => 'user' );
-            }
-            else {
-                $value = $date->DateTime;
-            }
-        }
-        else {
-            $RT::Logger->warn("$value is not a valid date string");
-        }
-    }
-
-    my $single_value = !$cf || !$cfid || $cf->SingleValue;
-
-    my $cfkey = $cfid ? $cfid : "$queue.$field";
-
-    if ( $null_op && !$column ) {
-        # IS[ NOT] NULL without column is the same as has[ no] any CF value,
-        # we can reuse our default joins for this operation
-        # with column specified we have different situation
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
-        $self->_OpenParen;
-        $self->Limit(
-            ALIAS    => $TicketCFs,
-            FIELD    => 'id',
-            OPERATOR => $op,
-            VALUE    => $value,
-            %rest
-        );
-        $self->Limit(
-            ALIAS      => $CFs,
-            FIELD      => 'Name',
-            OPERATOR   => 'IS NOT',
-            VALUE      => 'NULL',
-            QUOTEVALUE => 0,
-            ENTRYAGGREGATOR => 'AND',
-        ) if $CFs;
-        $self->_CloseParen;
-    }
-    elsif ( $op !~ /^[<>]=?$/ && (  $cf && $cf->Type eq 'IPAddressRange')) {
-    
-        my ($start_ip, $end_ip) = split /-/, $value;
-        
-        $self->_OpenParen;
-        if ( $op !~ /NOT|!=|<>/i ) { # positive equation
-            $self->_CustomFieldLimit(
-                'CF', '<=', $end_ip, %rest,
-                SUBKEY => $rest{'SUBKEY'}. '.Content',
-            );
-            $self->_CustomFieldLimit(
-                'CF', '>=', $start_ip, %rest,
-                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
-                ENTRYAGGREGATOR => 'AND',
-            ); 
-            # as well limit borders so DB optimizers can use better
-            # estimations and scan less rows
-# have to disable this tweak because of ipv6
-#            $self->_CustomFieldLimit(
-#                $field, '>=', '000.000.000.000', %rest,
-#                SUBKEY          => $rest{'SUBKEY'}. '.Content',
-#                ENTRYAGGREGATOR => 'AND',
-#            );
-#            $self->_CustomFieldLimit(
-#                $field, '<=', '255.255.255.255', %rest,
-#                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
-#                ENTRYAGGREGATOR => 'AND',
-#            );  
-        }       
-        else { # negative equation
-            $self->_CustomFieldLimit($field, '>', $end_ip, %rest);
-            $self->_CustomFieldLimit(
-                $field, '<', $start_ip, %rest,
-                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
-                ENTRYAGGREGATOR => 'OR',
-            );  
-            # TODO: as well limit borders so DB optimizers can use better
-            # estimations and scan less rows, but it's harder to do
-            # as we have OR aggregator
-        }
-        $self->_CloseParen;
-    } 
-    elsif ( !$negative_op || $single_value ) {
-        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
-
-        $self->_OpenParen;
-
-        $self->_OpenParen;
-
-        $self->_OpenParen;
-        # if column is defined then deal only with it
-        # otherwise search in Content and in LargeContent
-        if ( $column ) {
-            $self->Limit( $fix_op->(
-                ALIAS      => $TicketCFs,
-                FIELD      => $column,
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-                %rest
-            ) );
-            $self->_CloseParen;
-            $self->_CloseParen;
-            $self->_CloseParen;
-        }
-        else {
-            # need special treatment for Date
-            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
-                # no time specified, that means we want everything on a
-                # particular day.  in the database, we need to check for >
-                # and < the edges of that day.
-                    my $date = RT::Date->new( $self->CurrentUser );
-                    $date->Set( Format => 'unknown', Value => $value );
-                    my $daystart = $date->ISO;
-                    $date->AddDay;
-                    my $dayend = $date->ISO;
-
-                    $self->_OpenParen;
-
-                    $self->Limit(
-                        ALIAS    => $TicketCFs,
-                        FIELD    => 'Content',
-                        OPERATOR => ">=",
-                        VALUE    => $daystart,
-                        %rest,
-                    );
-
-                    $self->Limit(
-                        ALIAS    => $TicketCFs,
-                        FIELD    => 'Content',
-                        OPERATOR => "<",
-                        VALUE    => $dayend,
-                        %rest,
-                        ENTRYAGGREGATOR => 'AND',
-                    );
-
-                    $self->_CloseParen;
-            }
-            elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
-                if ( length( Encode::encode_utf8($value) ) < 256 ) {
-                    $self->Limit(
-                        ALIAS    => $TicketCFs,
-                        FIELD    => 'Content',
-                        OPERATOR => $op,
-                        VALUE    => $value,
-                        CASESENSITIVE => 0,
-                        %rest
-                    );
-                }
-                else {
-                    $self->_OpenParen;
-                    $self->Limit(
-                        ALIAS           => $TicketCFs,
-                        FIELD           => 'Content',
-                        OPERATOR        => '=',
-                        VALUE           => '',
-                        ENTRYAGGREGATOR => 'OR'
-                    );
-                    $self->Limit(
-                        ALIAS           => $TicketCFs,
-                        FIELD           => 'Content',
-                        OPERATOR        => 'IS',
-                        VALUE           => 'NULL',
-                        ENTRYAGGREGATOR => 'OR'
-                    );
-                    $self->_CloseParen;
-                    $self->Limit( $fix_op->(
-                        ALIAS           => $TicketCFs,
-                        FIELD           => 'LargeContent',
-                        OPERATOR        => $op,
-                        VALUE           => $value,
-                        ENTRYAGGREGATOR => 'AND',
-                        CASESENSITIVE => 0,
-                    ) );
-                }
-            }
-            else {
-                $self->Limit(
-                    ALIAS    => $TicketCFs,
-                    FIELD    => 'Content',
-                    OPERATOR => $op,
-                    VALUE    => $value,
-                    CASESENSITIVE => 0,
-                    %rest
-                );
-
-                $self->_OpenParen;
-                $self->_OpenParen;
-                $self->Limit(
-                    ALIAS           => $TicketCFs,
-                    FIELD           => 'Content',
-                    OPERATOR        => '=',
-                    VALUE           => '',
-                    ENTRYAGGREGATOR => 'OR'
-                );
-                $self->Limit(
-                    ALIAS           => $TicketCFs,
-                    FIELD           => 'Content',
-                    OPERATOR        => 'IS',
-                    VALUE           => 'NULL',
-                    ENTRYAGGREGATOR => 'OR'
-                );
-                $self->_CloseParen;
-                $self->Limit( $fix_op->(
-                    ALIAS           => $TicketCFs,
-                    FIELD           => 'LargeContent',
-                    OPERATOR        => $op,
-                    VALUE           => $value,
-                    ENTRYAGGREGATOR => 'AND',
-                    CASESENSITIVE => 0,
-                ) );
-                $self->_CloseParen;
-            }
-            $self->_CloseParen;
-
-            # XXX: if we join via CustomFields table then
-            # because of order of left joins we get NULLs in
-            # CF table and then get nulls for those records
-            # in OCFVs table what result in wrong results
-            # as decifer method now tries to load a CF then
-            # we fall into this situation only when there
-            # are more than one CF with the name in the DB.
-            # the same thing applies to order by call.
-            # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
-            # we want treat IS NULL as (not applies or has
-            # no value)
-            $self->Limit(
-                ALIAS           => $CFs,
-                FIELD           => 'Name',
-                OPERATOR        => 'IS NOT',
-                VALUE           => 'NULL',
-                QUOTEVALUE      => 0,
-                ENTRYAGGREGATOR => 'AND',
-            ) if $CFs;
-            $self->_CloseParen;
-
-            if ($negative_op) {
-                $self->Limit(
-                    ALIAS           => $TicketCFs,
-                    FIELD           => $column || 'Content',
-                    OPERATOR        => 'IS',
-                    VALUE           => 'NULL',
-                    QUOTEVALUE      => 0,
-                    ENTRYAGGREGATOR => 'OR',
-                );
-            }
-
-            $self->_CloseParen;
-        }
-    }
-    else {
-        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
-
-        # reverse operation
-        $op =~ s/!|NOT\s+//i;
-
-        # if column is defined then deal only with it
-        # otherwise search in Content and in LargeContent
-        if ( $column ) {
-            $self->Limit( $fix_op->(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
-                FIELD      => $column,
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            ) );
-        }
-        else {
-            $self->Limit(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
-                FIELD      => 'Content',
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            );
-        }
-        $self->Limit(
-            %rest,
-            ALIAS      => $TicketCFs,
-            FIELD      => 'id',
-            OPERATOR   => 'IS',
-            VALUE      => 'NULL',
-            QUOTEVALUE => 0,
-        );
-    }
+    $self->_LimitCustomField( $field, $queue, $cfid, $cf, $column, $op, $value, %rest );
 }
 
 sub _HasAttributeLimit {

commit 12fc2f4ac64c26e3d77bbd071ace76def757239a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:18:52 2013 -0500

    Rename $TicketCFs variable to be more general

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index bc47576..8c11caf 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -218,9 +218,9 @@ sub _CustomFieldJoin {
                  $self->{_sql_cf_alias}{$cfkey} );
     }
 
-    my ($TicketCFs, $CFs);
+    my ($ocfvalias, $CFs);
     if ( $cfid ) {
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+        $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
             ALIAS1 => 'main',
             FIELD1 => 'id',
@@ -228,7 +228,7 @@ sub _CustomFieldJoin {
             FIELD2 => 'ObjectId',
         );
         $self->Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ocfvalias,
             FIELD           => 'CustomField',
             VALUE           => $cfid,
             ENTRYAGGREGATOR => 'AND'
@@ -269,7 +269,7 @@ sub _CustomFieldJoin {
             VALUE           => $field,
         );
 
-        $TicketCFs = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
+        $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
             ALIAS1 => $CFs,
             FIELD1 => 'id',
@@ -277,7 +277,7 @@ sub _CustomFieldJoin {
             FIELD2 => 'CustomField',
         );
         $self->Limit(
-            LEFTJOIN        => $TicketCFs,
+            LEFTJOIN        => $ocfvalias,
             FIELD           => 'ObjectId',
             VALUE           => 'main.id',
             QUOTEVALUE      => 0,
@@ -285,20 +285,20 @@ sub _CustomFieldJoin {
         );
     }
     $self->Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ocfvalias,
         FIELD           => 'ObjectType',
         VALUE           => 'RT::Ticket',
         ENTRYAGGREGATOR => 'AND'
     );
     $self->Limit(
-        LEFTJOIN        => $TicketCFs,
+        LEFTJOIN        => $ocfvalias,
         FIELD           => 'Disabled',
         OPERATOR        => '=',
         VALUE           => '0',
         ENTRYAGGREGATOR => 'AND'
     );
 
-    return ($TicketCFs, $CFs);
+    return ($ocfvalias, $CFs);
 }
 
 sub LimitCustomField {
@@ -448,10 +448,10 @@ sub _LimitCustomField {
         # IS[ NOT] NULL without column is the same as has[ no] any CF value,
         # we can reuse our default joins for this operation
         # with column specified we have different situation
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
         $self->_OpenParen;
         $self->Limit(
-            ALIAS    => $TicketCFs,
+            ALIAS    => $ocfvalias,
             FIELD    => 'id',
             OPERATOR => $op,
             VALUE    => $value,
@@ -508,7 +508,7 @@ sub _LimitCustomField {
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
 
         $self->_OpenParen;
 
@@ -519,7 +519,7 @@ sub _LimitCustomField {
         # otherwise search in Content and in LargeContent
         if ( $column ) {
             $self->Limit( $fix_op->(
-                ALIAS      => $TicketCFs,
+                ALIAS      => $ocfvalias,
                 FIELD      => $column,
                 OPERATOR   => $op,
                 VALUE      => $value,
@@ -545,7 +545,7 @@ sub _LimitCustomField {
                 $self->_OpenParen;
 
                 $self->Limit(
-                    ALIAS    => $TicketCFs,
+                    ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => ">=",
                     VALUE    => $daystart,
@@ -553,7 +553,7 @@ sub _LimitCustomField {
                 );
 
                 $self->Limit(
-                    ALIAS    => $TicketCFs,
+                    ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => "<",
                     VALUE    => $dayend,
@@ -566,7 +566,7 @@ sub _LimitCustomField {
             elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
                 if ( length( Encode::encode_utf8($value) ) < 256 ) {
                     $self->Limit(
-                        ALIAS    => $TicketCFs,
+                        ALIAS    => $ocfvalias,
                         FIELD    => 'Content',
                         OPERATOR => $op,
                         VALUE    => $value,
@@ -577,14 +577,14 @@ sub _LimitCustomField {
                 else {
                     $self->_OpenParen;
                     $self->Limit(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ocfvalias,
                         FIELD           => 'Content',
                         OPERATOR        => '=',
                         VALUE           => '',
                         ENTRYAGGREGATOR => 'OR'
                     );
                     $self->Limit(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ocfvalias,
                         FIELD           => 'Content',
                         OPERATOR        => 'IS',
                         VALUE           => 'NULL',
@@ -592,7 +592,7 @@ sub _LimitCustomField {
                     );
                     $self->_CloseParen;
                     $self->Limit( $fix_op->(
-                        ALIAS           => $TicketCFs,
+                        ALIAS           => $ocfvalias,
                         FIELD           => 'LargeContent',
                         OPERATOR        => $op,
                         VALUE           => $value,
@@ -603,7 +603,7 @@ sub _LimitCustomField {
             }
             else {
                 $self->Limit(
-                    ALIAS    => $TicketCFs,
+                    ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => $op,
                     VALUE    => $value,
@@ -614,14 +614,14 @@ sub _LimitCustomField {
                 $self->_OpenParen;
                 $self->_OpenParen;
                 $self->Limit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ocfvalias,
                     FIELD           => 'Content',
                     OPERATOR        => '=',
                     VALUE           => '',
                     ENTRYAGGREGATOR => 'OR'
                 );
                 $self->Limit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ocfvalias,
                     FIELD           => 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
@@ -629,7 +629,7 @@ sub _LimitCustomField {
                 );
                 $self->_CloseParen;
                 $self->Limit( $fix_op->(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ocfvalias,
                     FIELD           => 'LargeContent',
                     OPERATOR        => $op,
                     VALUE           => $value,
@@ -663,7 +663,7 @@ sub _LimitCustomField {
 
             if ($negative_op) {
                 $self->Limit(
-                    ALIAS           => $TicketCFs,
+                    ALIAS           => $ocfvalias,
                     FIELD           => $column || 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
@@ -677,7 +677,7 @@ sub _LimitCustomField {
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;
@@ -686,8 +686,8 @@ sub _LimitCustomField {
         # otherwise search in Content and in LargeContent
         if ( $column ) {
             $self->Limit( $fix_op->(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
+                LEFTJOIN   => $ocfvalias,
+                ALIAS      => $ocfvalias,
                 FIELD      => $column,
                 OPERATOR   => $op,
                 VALUE      => $value,
@@ -696,8 +696,8 @@ sub _LimitCustomField {
         }
         else {
             $self->Limit(
-                LEFTJOIN   => $TicketCFs,
-                ALIAS      => $TicketCFs,
+                LEFTJOIN   => $ocfvalias,
+                ALIAS      => $ocfvalias,
                 FIELD      => 'Content',
                 OPERATOR   => $op,
                 VALUE      => $value,
@@ -706,7 +706,7 @@ sub _LimitCustomField {
         }
         $self->Limit(
             %rest,
-            ALIAS      => $TicketCFs,
+            ALIAS      => $ocfvalias,
             FIELD      => 'id',
             OPERATOR   => 'IS',
             VALUE      => 'NULL',

commit 84998f28400cd21fb7eaa07f17a7baa6b327a59b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:23:18 2013 -0500

    Simplify _CustomFieldJoin to take a key and a CF||string
    
    The logic surrounding the arguments for _CustomFieldJoin is more complex
    than necessary; simplify it by expecting either a CF name of a CF object.

diff --git a/lib/RT/Report/Tickets.pm b/lib/RT/Report/Tickets.pm
index c39fa90..3d3336c 100644
--- a/lib/RT/Report/Tickets.pm
+++ b/lib/RT/Report/Tickets.pm
@@ -246,7 +246,7 @@ sub _FieldToFunction {
         unless ( $cf->id ) {
             $RT::Logger->error("Couldn't load CustomField #$cf_name");
         } else {
-            my ($ticket_cf_alias, $cf_alias) = $self->_CustomFieldJoin($cf->id, $cf->id, $cf_name);
+            my ($ticket_cf_alias, $cf_alias) = $self->_CustomFieldJoin($cf->id, $cf);
             @args{qw(ALIAS FIELD)} = ($ticket_cf_alias, 'Content');
         }
     } elsif ( $field =~ /^(?:(Owner|Creator|LastUpdatedBy))(?:\.(.*))?$/ ) {
diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 8c11caf..0e8dccd 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -72,6 +72,8 @@ use base qw(DBIx::SearchBuilder RT::Base);
 use RT::Base;
 use DBIx::SearchBuilder "1.40";
 
+use Scalar::Util qw/blessed/;
+
 sub _Init  {
     my $self = shift;
     
@@ -209,7 +211,7 @@ Factor out the Join of custom fields so we can use it for sorting too
 =cut
 
 sub _CustomFieldJoin {
-    my ($self, $cfkey, $cfid, $field) = @_;
+    my ($self, $cfkey, $cf) = @_;
     # Perform one Join per CustomField
     if ( $self->{_sql_object_cfv_alias}{$cfkey} ||
          $self->{_sql_cf_alias}{$cfkey} )
@@ -219,7 +221,7 @@ sub _CustomFieldJoin {
     }
 
     my ($ocfvalias, $CFs);
-    if ( $cfid ) {
+    if ( blessed($cf) ) {
         $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
             TYPE   => 'LEFT',
             ALIAS1 => 'main',
@@ -230,7 +232,7 @@ sub _CustomFieldJoin {
         $self->Limit(
             LEFTJOIN        => $ocfvalias,
             FIELD           => 'CustomField',
-            VALUE           => $cfid,
+            VALUE           => $cf->id,
             ENTRYAGGREGATOR => 'AND'
         );
     }
@@ -266,7 +268,7 @@ sub _CustomFieldJoin {
             LEFTJOIN        => $CFs,
             ENTRYAGGREGATOR => 'AND',
             FIELD           => 'Name',
-            VALUE           => $field,
+            VALUE           => $cf,
         );
 
         $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
@@ -448,7 +450,7 @@ sub _LimitCustomField {
         # IS[ NOT] NULL without column is the same as has[ no] any CF value,
         # we can reuse our default joins for this operation
         # with column specified we have different situation
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
         $self->_OpenParen;
         $self->Limit(
             ALIAS    => $ocfvalias,
@@ -508,7 +510,7 @@ sub _LimitCustomField {
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
 
         $self->_OpenParen;
 
@@ -677,7 +679,7 @@ sub _LimitCustomField {
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cfid, $field );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 26b84bf..3a8e6e1 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1212,7 +1212,7 @@ sub OrderByCols {
            my ($queue, $field, $cf_obj, $column) = $self->_CustomFieldDecipher( $subkey );
            my $cfkey = $cf_obj ? $cf_obj->id : "$queue.$field";
            $cfkey .= ".ordering" if !$cf_obj || ($cf_obj->MaxValues||0) != 1;
-           my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj ?$cf_obj->id :0) , $field );
+           my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj || $field) );
            # this is described in _CustomFieldLimit
            $self->Limit(
                ALIAS      => $CFs,

commit d799fe63fd85ca4576e613c1ec8e4fc4d98e3fb7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:29:18 2013 -0500

    Switch to a more standard paramhash for arguments
    
    This also collapses the $cfid, $cf, $queue, and $field arguments down
    into the more understandable KEY and CUSTOMFIELD.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 0e8dccd..a750ccb 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -348,8 +348,22 @@ use Regexp::Common::net::CIDR;
 
 sub _LimitCustomField {
     my $self = shift;
-    my ( $field, $queue, $cfid, $cf, $column, $op, $value, %rest ) = @_;
+    my %args = ( VALUE        => undef,
+                 CUSTOMFIELD  => undef,
+                 OPERATOR     => '=',
+                 KEY          => undef,
+                 @_ );
 
+    my $op     = delete $args{OPERATOR};
+    my $value  = delete $args{VALUE};
+    my $cf     = delete $args{CUSTOMFIELD};
+    my $column = delete $args{COLUMN};
+    my $cfkey  = delete $args{KEY};
+    if (blessed($cf) and $cf->id) {
+        $cfkey ||= $cf->id;
+    } else {
+        $cfkey ||= $cf;
+    }
 
 # If we're trying to find custom fields that don't match something, we
 # want tickets where the custom field has no value at all.  Note that
@@ -378,7 +392,7 @@ sub _LimitCustomField {
         return %args;
     };
 
-    if ( $cf && $cf->Type eq 'IPAddress' ) {
+    if ( blessed($cf) && $cf->Type eq 'IPAddress' ) {
         my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
         if ($parsed) {
             $value = $parsed;
@@ -388,7 +402,7 @@ sub _LimitCustomField {
         }
     }
 
-    if ( $cf && $cf->Type eq 'IPAddressRange' ) {
+    if ( blessed($cf) && $cf->Type eq 'IPAddressRange' ) {
 
         if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
 
@@ -419,7 +433,7 @@ sub _LimitCustomField {
         }
     }
 
-    if ( $cf && $cf->Type =~ /^Date(?:Time)?$/ ) {
+    if ( blessed($cf) && $cf->Type =~ /^Date(?:Time)?$/ ) {
         my $date = RT::Date->new( $self->CurrentUser );
         $date->Set( Format => 'unknown', Value => $value );
         if ( $date->Unix ) {
@@ -442,22 +456,20 @@ sub _LimitCustomField {
         }
     }
 
-    my $single_value = !$cf || !$cfid || $cf->SingleValue;
-
-    my $cfkey = $cfid ? $cfid : "$queue.$field";
+    my $single_value = !blessed($cf) || $cf->SingleValue;
 
     if ( $null_op && !$column ) {
         # IS[ NOT] NULL without column is the same as has[ no] any CF value,
         # we can reuse our default joins for this operation
         # with column specified we have different situation
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
         $self->_OpenParen;
         $self->Limit(
             ALIAS    => $ocfvalias,
             FIELD    => 'id',
             OPERATOR => $op,
             VALUE    => $value,
-            %rest
+            %args
         );
         $self->Limit(
             ALIAS      => $CFs,
@@ -469,19 +481,26 @@ sub _LimitCustomField {
         ) if $CFs;
         $self->_CloseParen;
     }
-    elsif ( $op !~ /^[<>]=?$/ && (  $cf && $cf->Type eq 'IPAddressRange')) {
-    
+    elsif ( $op !~ /^[<>]=?$/ && (  blessed($cf) && $cf->Type eq 'IPAddressRange')) {
         my ($start_ip, $end_ip) = split /-/, $value;
         
         $self->_OpenParen;
         if ( $op !~ /NOT|!=|<>/i ) { # positive equation
             $self->_LimitCustomField(
-                $field, $queue, $cfid, $cf, 'Content', '<=', $end_ip, %rest,
+                OPERATOR    => '<=',
+                VALUE       => $end_ip,
+                CUSTOMFIELD => $cf,
+                COLUMN      => 'Content',
+                %args,
             );
             $self->_LimitCustomField(
-                $field, $queue, $cfid, $cf, 'LargeContent', '>=', $start_ip, %rest,
+                OPERATOR    => '>=',
+                VALUE       => $start_ip,
+                CUSTOMFIELD => $cf,
+                COLUMN      => 'LargeContent',
+                %args,
                 ENTRYAGGREGATOR => 'AND',
-            ); 
+            );
             # as well limit borders so DB optimizers can use better
             # estimations and scan less rows
 # have to disable this tweak because of ipv6
@@ -497,11 +516,21 @@ sub _LimitCustomField {
 #            );  
         }       
         else { # negative equation
-            $self->_LimitCustomField( $field, $queue, $cfid, $cf, 'Content', '>', $end_ip, %rest);
             $self->_LimitCustomField(
-                $field, $queue, $cfid, $cf, 'LargeContent', '<', $start_ip, %rest,
+                OPERATOR    => '>',
+                VALUE       => $end_ip,
+                CUSTOMFIELD => $cf,
+                COLUMN      => 'Content',
+                %args,
+            );
+            $self->_LimitCustomField(
+                OPERATOR    => '<',
+                VALUE       => $start_ip,
+                CUSTOMFIELD => $cf,
+                COLUMN      => 'LargeContent',
+                %args,
                 ENTRYAGGREGATOR => 'OR',
-            );  
+            );
             # TODO: as well limit borders so DB optimizers can use better
             # estimations and scan less rows, but it's harder to do
             # as we have OR aggregator
@@ -510,7 +539,7 @@ sub _LimitCustomField {
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
         $self->_OpenParen;
 
@@ -526,7 +555,7 @@ sub _LimitCustomField {
                 OPERATOR   => $op,
                 VALUE      => $value,
                 CASESENSITIVE => 0,
-                %rest
+                %args
             ) );
             $self->_CloseParen;
             $self->_CloseParen;
@@ -534,7 +563,7 @@ sub _LimitCustomField {
         }
         else {
             # need special treatment for Date
-            if ( $cf and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
+            if ( blessed($cf) and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
                 # no time specified, that means we want everything on a
                 # particular day.  in the database, we need to check for >
                 # and < the edges of that day.
@@ -551,7 +580,7 @@ sub _LimitCustomField {
                     FIELD    => 'Content',
                     OPERATOR => ">=",
                     VALUE    => $daystart,
-                    %rest,
+                    %args,
                 );
 
                 $self->Limit(
@@ -559,7 +588,7 @@ sub _LimitCustomField {
                     FIELD    => 'Content',
                     OPERATOR => "<",
                     VALUE    => $dayend,
-                    %rest,
+                    %args,
                     ENTRYAGGREGATOR => 'AND',
                 );
 
@@ -573,7 +602,7 @@ sub _LimitCustomField {
                         OPERATOR => $op,
                         VALUE    => $value,
                         CASESENSITIVE => 0,
-                        %rest
+                        %args
                     );
                 }
                 else {
@@ -610,7 +639,7 @@ sub _LimitCustomField {
                     OPERATOR => $op,
                     VALUE    => $value,
                     CASESENSITIVE => 0,
-                    %rest
+                    %args
                 );
 
                 $self->_OpenParen;
@@ -679,7 +708,7 @@ sub _LimitCustomField {
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf || $field) );
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
         # reverse operation
         $op =~ s/!|NOT\s+//i;
@@ -707,7 +736,7 @@ sub _LimitCustomField {
             );
         }
         $self->Limit(
-            %rest,
+            %args,
             ALIAS      => $ocfvalias,
             FIELD      => 'id',
             OPERATOR   => 'IS',
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 3a8e6e1..cb7f6da 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1101,9 +1101,17 @@ sub _CustomFieldLimit {
 
     my ($queue, $cfid, $cf, $column);
     ($queue, $field, $cf, $column) = $self->_CustomFieldDecipher( $field );
-    $cfid = $cf ? $cf->id  : 0 ;
 
-    $self->_LimitCustomField( $field, $queue, $cfid, $cf, $column, $op, $value, %rest );
+
+    $self->_LimitCustomField(
+        %rest,
+        CUSTOMFIELD => $cf || $field,
+        KEY      => $cf ? $cf->id : "$queue.$field",
+        OPERATOR => $op,
+        VALUE    => $value,
+        COLUMN   => $column,
+        SUBCLAUSE => "ticketsql",
+    );
 }
 
 sub _HasAttributeLimit {

commit 4dff5928064de2375399b0b37f1069ff7485d2a7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 19:07:54 2013 -0400

    Prevent confusion about what %args can override
    
    As the basic arguments are delete()'d from %args, all the remains
    therein are extra arguments, like ENTRYAGGREGATOR or SUBCLAUSE.  Clarify
    the precedence of the arguments by moving %args to the beginning of the
    Limit() call; this changes no functionality.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index a750ccb..285a8ac 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -465,11 +465,11 @@ sub _LimitCustomField {
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
         $self->_OpenParen;
         $self->Limit(
+            %args,
             ALIAS    => $ocfvalias,
             FIELD    => 'id',
             OPERATOR => $op,
             VALUE    => $value,
-            %args
         );
         $self->Limit(
             ALIAS      => $CFs,
@@ -487,18 +487,18 @@ sub _LimitCustomField {
         $self->_OpenParen;
         if ( $op !~ /NOT|!=|<>/i ) { # positive equation
             $self->_LimitCustomField(
+                %args,
                 OPERATOR    => '<=',
                 VALUE       => $end_ip,
                 CUSTOMFIELD => $cf,
                 COLUMN      => 'Content',
-                %args,
             );
             $self->_LimitCustomField(
+                %args,
                 OPERATOR    => '>=',
                 VALUE       => $start_ip,
                 CUSTOMFIELD => $cf,
                 COLUMN      => 'LargeContent',
-                %args,
                 ENTRYAGGREGATOR => 'AND',
             );
             # as well limit borders so DB optimizers can use better
@@ -517,18 +517,18 @@ sub _LimitCustomField {
         }       
         else { # negative equation
             $self->_LimitCustomField(
+                %args,
                 OPERATOR    => '>',
                 VALUE       => $end_ip,
                 CUSTOMFIELD => $cf,
                 COLUMN      => 'Content',
-                %args,
             );
             $self->_LimitCustomField(
+                %args,
                 OPERATOR    => '<',
                 VALUE       => $start_ip,
                 CUSTOMFIELD => $cf,
                 COLUMN      => 'LargeContent',
-                %args,
                 ENTRYAGGREGATOR => 'OR',
             );
             # TODO: as well limit borders so DB optimizers can use better
@@ -550,12 +550,12 @@ sub _LimitCustomField {
         # otherwise search in Content and in LargeContent
         if ( $column ) {
             $self->Limit( $fix_op->(
+                %args,
                 ALIAS      => $ocfvalias,
                 FIELD      => $column,
                 OPERATOR   => $op,
                 VALUE      => $value,
                 CASESENSITIVE => 0,
-                %args
             ) );
             $self->_CloseParen;
             $self->_CloseParen;
@@ -576,19 +576,19 @@ sub _LimitCustomField {
                 $self->_OpenParen;
 
                 $self->Limit(
+                    %args,
                     ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => ">=",
                     VALUE    => $daystart,
-                    %args,
                 );
 
                 $self->Limit(
+                    %args,
                     ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => "<",
                     VALUE    => $dayend,
-                    %args,
                     ENTRYAGGREGATOR => 'AND',
                 );
 
@@ -597,12 +597,12 @@ sub _LimitCustomField {
             elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
                 if ( length( Encode::encode_utf8($value) ) < 256 ) {
                     $self->Limit(
+                        %args,
                         ALIAS    => $ocfvalias,
                         FIELD    => 'Content',
                         OPERATOR => $op,
                         VALUE    => $value,
                         CASESENSITIVE => 0,
-                        %args
                     );
                 }
                 else {
@@ -634,12 +634,12 @@ sub _LimitCustomField {
             }
             else {
                 $self->Limit(
+                    %args,
                     ALIAS    => $ocfvalias,
                     FIELD    => 'Content',
                     OPERATOR => $op,
                     VALUE    => $value,
                     CASESENSITIVE => 0,
-                    %args
                 );
 
                 $self->_OpenParen;

commit 8ee65ec20ab1b85b3895d84069f5b3671e4e7711
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:30:20 2013 -0500

    Generalize _CustomFieldJoin somewhat
    
    This replaces explicit RT::Ticket ObjectTypes and LookupTypes with their
    more general forms.  It also factors out the concept of listing all
    applicable CFs that match a given name -- which may be additionally
    limited by queues, in the case of tickets.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 285a8ac..f77e01c 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -237,59 +237,14 @@ sub _CustomFieldJoin {
         );
     }
     else {
-        my $ocfalias = $self->Join(
-            TYPE       => 'LEFT',
-            FIELD1     => 'Queue',
-            TABLE2     => 'ObjectCustomFields',
-            FIELD2     => 'ObjectId',
-        );
-
-        $self->Limit(
-            LEFTJOIN        => $ocfalias,
-            ENTRYAGGREGATOR => 'OR',
-            FIELD           => 'ObjectId',
-            VALUE           => '0',
-        );
-
-        $CFs = $self->{_sql_cf_alias}{$cfkey} = $self->Join(
-            TYPE       => 'LEFT',
-            ALIAS1     => $ocfalias,
-            FIELD1     => 'CustomField',
-            TABLE2     => 'CustomFields',
-            FIELD2     => 'id',
-        );
-        $self->Limit(
-            LEFTJOIN        => $CFs,
-            ENTRYAGGREGATOR => 'AND',
-            FIELD           => 'LookupType',
-            VALUE           => 'RT::Queue-RT::Ticket',
-        );
-        $self->Limit(
-            LEFTJOIN        => $CFs,
-            ENTRYAGGREGATOR => 'AND',
-            FIELD           => 'Name',
-            VALUE           => $cf,
-        );
-
-        $ocfvalias = $self->{_sql_object_cfv_alias}{$cfkey} = $self->Join(
-            TYPE   => 'LEFT',
-            ALIAS1 => $CFs,
-            FIELD1 => 'id',
-            TABLE2 => 'ObjectCustomFieldValues',
-            FIELD2 => 'CustomField',
-        );
-        $self->Limit(
-            LEFTJOIN        => $ocfvalias,
-            FIELD           => 'ObjectId',
-            VALUE           => 'main.id',
-            QUOTEVALUE      => 0,
-            ENTRYAGGREGATOR => 'AND',
-        );
+        ($ocfvalias, $CFs) = $self->_CustomFieldJoinByName( $cf );
+        $self->{_sql_cf_alias}{$cfkey} = $CFs;
+        $self->{_sql_object_cfv_alias}{$cfkey} = $ocfvalias;
     }
     $self->Limit(
         LEFTJOIN        => $ocfvalias,
         FIELD           => 'ObjectType',
-        VALUE           => 'RT::Ticket',
+        VALUE           => ref($self->NewItem),
         ENTRYAGGREGATOR => 'AND'
     );
     $self->Limit(
@@ -303,6 +258,54 @@ sub _CustomFieldJoin {
     return ($ocfvalias, $CFs);
 }
 
+sub _CustomFieldJoinByName {
+    my $self = shift;
+    my ($cf) = @_;
+    my $ocfalias = $self->Join(
+        TYPE       => 'LEFT',
+        EXPRESSION => q|'0'|,
+        TABLE2     => 'ObjectCustomFields',
+        FIELD2     => 'ObjectId',
+    );
+
+    my $CFs = $self->Join(
+        TYPE       => 'LEFT',
+        ALIAS1     => $ocfalias,
+        FIELD1     => 'CustomField',
+        TABLE2     => 'CustomFields',
+        FIELD2     => 'id',
+    );
+    $self->Limit(
+        LEFTJOIN        => $CFs,
+        ENTRYAGGREGATOR => 'AND',
+        FIELD           => 'LookupType',
+        VALUE           => $self->NewItem->CustomFieldLookupType,
+    );
+    $self->Limit(
+        LEFTJOIN        => $CFs,
+        ENTRYAGGREGATOR => 'AND',
+        FIELD           => 'Name',
+        VALUE           => $cf,
+    );
+
+    my $ocfvalias = $self->Join(
+        TYPE   => 'LEFT',
+        ALIAS1 => $CFs,
+        FIELD1 => 'id',
+        TABLE2 => 'ObjectCustomFieldValues',
+        FIELD2 => 'CustomField',
+    );
+    $self->Limit(
+        LEFTJOIN        => $ocfvalias,
+        FIELD           => 'ObjectId',
+        VALUE           => 'main.id',
+        QUOTEVALUE      => 0,
+        ENTRYAGGREGATOR => 'AND',
+    );
+
+    return ($ocfvalias, $CFs, $ocfalias);
+}
+
 sub LimitCustomField {
     my $self = shift;
     my %args = ( VALUE        => undef,
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index cb7f6da..ed00341 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -86,6 +86,8 @@ use base 'RT::SearchBuilder';
 use Role::Basic 'with';
 with 'RT::SearchBuilder::Role::Roles';
 
+use Scalar::Util qw/blessed/;
+
 use RT::Ticket;
 use RT::SQL;
 
@@ -1114,6 +1116,21 @@ sub _CustomFieldLimit {
     );
 }
 
+sub _CustomFieldJoinByName {
+    my $self = shift;
+    my ($cf) = @_;
+
+    my ($ocfvalias, $CFs, $ocfalias) = $self->SUPER::_CustomFieldJoinByName($cf);
+    $self->Limit(
+        LEFTJOIN        => $ocfalias,
+        ENTRYAGGREGATOR => 'OR',
+        FIELD           => 'ObjectId',
+        VALUE           => 'main.Queue',
+        QUOTEVALUE      => 0,
+    );
+    return ($ocfvalias, $CFs, $ocfalias);
+}
+
 sub _HasAttributeLimit {
     my ( $self, $field, $op, $value, %rest ) = @_;
 

commit c558d15e413c6fc0588dd474e974bcfebd3dc81f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:31:32 2013 -0500

    Add explicit subclauses to all limits
    
    RT::Tickets assumes a subclause of "ticketsql" for all Limit and
    _OpenParen / _CloseParen calls.  Allow calling on non-Tickets
    collections by providing a default SUBCLAUSE name, and performing all
    Limit, _OpenParen, and _CloseParen adjustments therein.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index f77e01c..abaea52 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -368,6 +368,8 @@ sub _LimitCustomField {
         $cfkey ||= $cf;
     }
 
+    $args{SUBCLAUSE} ||= "cf-$cfkey";
+
 # If we're trying to find custom fields that don't match something, we
 # want tickets where the custom field has no value at all.  Note that
 # we explicitly don't include the "IS NULL" case, since we would
@@ -466,7 +468,7 @@ sub _LimitCustomField {
         # we can reuse our default joins for this operation
         # with column specified we have different situation
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
-        $self->_OpenParen;
+        $self->_OpenParen( $args{SUBCLAUSE} );
         $self->Limit(
             %args,
             ALIAS    => $ocfvalias,
@@ -481,13 +483,13 @@ sub _LimitCustomField {
             VALUE      => 'NULL',
             QUOTEVALUE => 0,
             ENTRYAGGREGATOR => 'AND',
+            SUBCLAUSE  => $args{SUBCLAUSE},
         ) if $CFs;
-        $self->_CloseParen;
+        $self->_CloseParen( $args{SUBCLAUSE} );
     }
     elsif ( $op !~ /^[<>]=?$/ && (  blessed($cf) && $cf->Type eq 'IPAddressRange')) {
         my ($start_ip, $end_ip) = split /-/, $value;
-        
-        $self->_OpenParen;
+        $self->_OpenParen( $args{SUBCLAUSE} );
         if ( $op !~ /NOT|!=|<>/i ) { # positive equation
             $self->_LimitCustomField(
                 %args,
@@ -538,17 +540,15 @@ sub _LimitCustomField {
             # estimations and scan less rows, but it's harder to do
             # as we have OR aggregator
         }
-        $self->_CloseParen;
+        $self->_CloseParen( $args{SUBCLAUSE} );
     } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
-        $self->_OpenParen;
-
-        $self->_OpenParen;
-
-        $self->_OpenParen;
+        $self->_OpenParen( $args{SUBCLAUSE} );
+        $self->_OpenParen( $args{SUBCLAUSE} );
+        $self->_OpenParen( $args{SUBCLAUSE} );
         # if column is defined then deal only with it
         # otherwise search in Content and in LargeContent
         if ( $column ) {
@@ -560,9 +560,9 @@ sub _LimitCustomField {
                 VALUE      => $value,
                 CASESENSITIVE => 0,
             ) );
-            $self->_CloseParen;
-            $self->_CloseParen;
-            $self->_CloseParen;
+            $self->_CloseParen( $args{SUBCLAUSE} );
+            $self->_CloseParen( $args{SUBCLAUSE} );
+            $self->_CloseParen( $args{SUBCLAUSE} );
         }
         else {
             # need special treatment for Date
@@ -576,7 +576,7 @@ sub _LimitCustomField {
                 $date->AddDay;
                 my $dayend = $date->ISO;
 
-                $self->_OpenParen;
+                $self->_OpenParen( $args{SUBCLAUSE} );
 
                 $self->Limit(
                     %args,
@@ -595,7 +595,7 @@ sub _LimitCustomField {
                     ENTRYAGGREGATOR => 'AND',
                 );
 
-                $self->_CloseParen;
+                $self->_CloseParen( $args{SUBCLAUSE} );
             }
             elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
                 if ( length( Encode::encode_utf8($value) ) < 256 ) {
@@ -609,28 +609,31 @@ sub _LimitCustomField {
                     );
                 }
                 else {
-                    $self->_OpenParen;
+                    $self->_OpenParen( $args{SUBCLAUSE} );
                     $self->Limit(
                         ALIAS           => $ocfvalias,
                         FIELD           => 'Content',
                         OPERATOR        => '=',
                         VALUE           => '',
-                        ENTRYAGGREGATOR => 'OR'
+                        ENTRYAGGREGATOR => 'OR',
+                        SUBCLAUSE       => $args{SUBCLAUSE},
                     );
                     $self->Limit(
                         ALIAS           => $ocfvalias,
                         FIELD           => 'Content',
                         OPERATOR        => 'IS',
                         VALUE           => 'NULL',
-                        ENTRYAGGREGATOR => 'OR'
+                        ENTRYAGGREGATOR => 'OR',
+                        SUBCLAUSE       => $args{SUBCLAUSE},
                     );
-                    $self->_CloseParen;
+                    $self->_CloseParen( $args{SUBCLAUSE} );
                     $self->Limit( $fix_op->(
                         ALIAS           => $ocfvalias,
                         FIELD           => 'LargeContent',
                         OPERATOR        => $op,
                         VALUE           => $value,
                         ENTRYAGGREGATOR => 'AND',
+                        SUBCLAUSE       => $args{SUBCLAUSE},
                         CASESENSITIVE => 0,
                     ) );
                 }
@@ -645,13 +648,14 @@ sub _LimitCustomField {
                     CASESENSITIVE => 0,
                 );
 
-                $self->_OpenParen;
-                $self->_OpenParen;
+                $self->_OpenParen( $args{SUBCLAUSE} );
+                $self->_OpenParen( $args{SUBCLAUSE} );
                 $self->Limit(
                     ALIAS           => $ocfvalias,
                     FIELD           => 'Content',
                     OPERATOR        => '=',
                     VALUE           => '',
+                    SUBCLAUSE       => $args{SUBCLAUSE},
                     ENTRYAGGREGATOR => 'OR'
                 );
                 $self->Limit(
@@ -659,20 +663,22 @@ sub _LimitCustomField {
                     FIELD           => 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
+                    SUBCLAUSE       => $args{SUBCLAUSE},
                     ENTRYAGGREGATOR => 'OR'
                 );
-                $self->_CloseParen;
+                $self->_CloseParen( $args{SUBCLAUSE} );
                 $self->Limit( $fix_op->(
                     ALIAS           => $ocfvalias,
                     FIELD           => 'LargeContent',
                     OPERATOR        => $op,
                     VALUE           => $value,
                     ENTRYAGGREGATOR => 'AND',
+                    SUBCLAUSE       => $args{SUBCLAUSE},
                     CASESENSITIVE => 0,
                 ) );
-                $self->_CloseParen;
+                $self->_CloseParen( $args{SUBCLAUSE} );
             }
-            $self->_CloseParen;
+            $self->_CloseParen( $args{SUBCLAUSE} );
 
             # XXX: if we join via CustomFields table then
             # because of order of left joins we get NULLs in
@@ -692,8 +698,9 @@ sub _LimitCustomField {
                 VALUE           => 'NULL',
                 QUOTEVALUE      => 0,
                 ENTRYAGGREGATOR => 'AND',
+                SUBCLAUSE       => $args{SUBCLAUSE},
             ) if $CFs;
-            $self->_CloseParen;
+            $self->_CloseParen( $args{SUBCLAUSE} );
 
             if ($negative_op) {
                 $self->Limit(
@@ -703,10 +710,11 @@ sub _LimitCustomField {
                     VALUE           => 'NULL',
                     QUOTEVALUE      => 0,
                     ENTRYAGGREGATOR => 'OR',
+                    SUBCLAUSE       => $args{SUBCLAUSE},
                 );
             }
 
-            $self->_CloseParen;
+            $self->_CloseParen( $args{SUBCLAUSE} );
         }
     }
     else {
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index ed00341..694cdae 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1328,10 +1328,10 @@ sub _SQLJoin {
 }
 
 sub _OpenParen {
-    $_[0]->SUPER::_OpenParen( 'ticketsql' );
+    $_[0]->SUPER::_OpenParen( $_[1] || 'ticketsql' );
 }
 sub _CloseParen {
-    $_[0]->SUPER::_CloseParen( 'ticketsql' );
+    $_[0]->SUPER::_CloseParen( $_[1] || 'ticketsql' );
 }
 
 sub Limit {

commit 53b2a9a8d6974f21fff475f1d1c5f4df2ca96bb3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sat Apr 27 00:40:55 2013 -0400

    Treat CF ids as specific identifiers like objects, not as names

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index abaea52..0cedc0f 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -364,6 +364,15 @@ sub _LimitCustomField {
     my $cfkey  = delete $args{KEY};
     if (blessed($cf) and $cf->id) {
         $cfkey ||= $cf->id;
+    } elsif ($cf =~ /^\d+$/) {
+        my $obj = RT::CustomField->new( $self->CurrentUser );
+        $obj->Load($cf);
+        if ($obj->id) {
+            $cf = $obj;
+            $cfkey ||= $cf->id;
+        } else {
+            $cfkey ||= $cf;
+        }
     } else {
         $cfkey ||= $cf;
     }

commit 3a70b3c8127220ab3a557340e94d322e1a7b74fc
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 01:32:04 2013 -0500

    Switch the general LimitCustomField to the new implementation
    
    Use the new _LimitCustomField implementation for RT::SearchBuilder's
    LimitCustomField.  The former is not simply renamed to the latter
    because RT::Tickets contains a LimitCustomField which acts on
    "restrictions", and must continue to do so.  As such, _LimitCustomField
    (and its recursive calls) must continue to occupy some other name.
    
    This removes the (dubious and unused) ability to call LimitCustomField
    with no CUSTOMFIELD provided, which searches within all custom fields.
    It adds all of the complex date and IP parsing capabilities that Tickets
    have enjoyed.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 0cedc0f..9340637 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -308,42 +308,7 @@ sub _CustomFieldJoinByName {
 
 sub LimitCustomField {
     my $self = shift;
-    my %args = ( VALUE        => undef,
-                 CUSTOMFIELD  => undef,
-                 OPERATOR     => '=',
-                 @_ );
-
-    my $alias = $self->Join(
-        TYPE       => 'left',
-        ALIAS1     => 'main',
-        FIELD1     => 'id',
-        TABLE2     => 'ObjectCustomFieldValues',
-        FIELD2     => 'ObjectId'
-    );
-    $self->Limit(
-        ALIAS      => $alias,
-        FIELD      => 'CustomField',
-        OPERATOR   => '=',
-        VALUE      => $args{'CUSTOMFIELD'},
-    ) if ($args{'CUSTOMFIELD'});
-    $self->Limit(
-        ALIAS      => $alias,
-        FIELD      => 'ObjectType',
-        OPERATOR   => '=',
-        VALUE      => $self->_SingularClass,
-    );
-    $self->Limit(
-        ALIAS      => $alias,
-        FIELD      => 'Content',
-        OPERATOR   => $args{'OPERATOR'},
-        VALUE      => $args{'VALUE'},
-    );
-    $self->Limit(
-        ALIAS => $alias,
-        FIELD => 'Disabled',
-        OPERATOR => '=',
-        VALUE => 0,
-    );
+    return $self->_LimitCustomField( @_ );
 }
 
 use Regexp::Common qw(RE_net_IPv4);

commit 0f6ee87be9a7c399d18e8c05f9ad90888b9ff6d2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jan 29 15:08:45 2013 -0500

    Refactor CF ordering into a method on SearchBuilder

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 9340637..3b2d637 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -121,6 +121,51 @@ sub JoinTransactions {
     return $alias;
 }
 
+sub _OrderByCF {
+    my $self = shift;
+    my ($row, $cf) = @_;
+
+    my $cfkey = blessed($cf) ? $cf->id : $cf;
+    $cfkey .= ".ordering" if !blessed($cf) || ($cf->MaxValues||0) != 1;
+    my ($ocfvs, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
+    # this is described in _LimitCustomField
+    $self->Limit(
+        ALIAS      => $CFs,
+        FIELD      => 'Name',
+        OPERATOR   => 'IS NOT',
+        VALUE      => 'NULL',
+        QUOTEVALUE => 1,
+        ENTRYAGGREGATOR => 'AND',
+    ) if $CFs;
+    unless (blessed($cf)) {
+        # For those cases where we are doing a join against the
+        # CF name, and don't have a CFid, use Unique to make sure
+        # we don't show duplicate tickets.  NOTE: I'm pretty sure
+        # this will stay mixed in for the life of the
+        # class/package, and not just for the life of the object.
+        # Potential performance issue.
+        require DBIx::SearchBuilder::Unique;
+        DBIx::SearchBuilder::Unique->import;
+    }
+    my $CFvs = $self->Join(
+        TYPE   => 'LEFT',
+        ALIAS1 => $ocfvs,
+        FIELD1 => 'CustomField',
+        TABLE2 => 'CustomFieldValues',
+        FIELD2 => 'CustomField',
+    );
+    $self->Limit(
+        LEFTJOIN        => $CFvs,
+        FIELD           => 'Name',
+        QUOTEVALUE      => 0,
+        VALUE           => "$ocfvs.Content",
+        ENTRYAGGREGATOR => 'AND'
+    );
+
+    return { %$row, ALIAS => $CFvs,  FIELD => 'SortOrder' },
+           { %$row, ALIAS => $ocfvs, FIELD => 'Content' };
+}
+
 sub OrderByCols {
     my $self = shift;
     my @sort;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 694cdae..9d3abf1 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -1234,46 +1234,8 @@ sub OrderByCols {
             }
             push @res, { %$row, ALIAS => $users, FIELD => $subkey };
        } elsif ( defined $meta->[0] && $meta->[0] eq 'CUSTOMFIELD' ) {
-           my ($queue, $field, $cf_obj, $column) = $self->_CustomFieldDecipher( $subkey );
-           my $cfkey = $cf_obj ? $cf_obj->id : "$queue.$field";
-           $cfkey .= ".ordering" if !$cf_obj || ($cf_obj->MaxValues||0) != 1;
-           my ($TicketCFs, $CFs) = $self->_CustomFieldJoin( $cfkey, ($cf_obj || $field) );
-           # this is described in _CustomFieldLimit
-           $self->Limit(
-               ALIAS      => $CFs,
-               FIELD      => 'Name',
-               OPERATOR   => 'IS NOT',
-               VALUE      => 'NULL',
-               QUOTEVALUE => 1,
-               ENTRYAGGREGATOR => 'AND',
-           ) if $CFs;
-           unless ($cf_obj) {
-               # For those cases where we are doing a join against the
-               # CF name, and don't have a CFid, use Unique to make sure
-               # we don't show duplicate tickets.  NOTE: I'm pretty sure
-               # this will stay mixed in for the life of the
-               # class/package, and not just for the life of the object.
-               # Potential performance issue.
-               require DBIx::SearchBuilder::Unique;
-               DBIx::SearchBuilder::Unique->import;
-           }
-           my $CFvs = $self->Join(
-               TYPE   => 'LEFT',
-               ALIAS1 => $TicketCFs,
-               FIELD1 => 'CustomField',
-               TABLE2 => 'CustomFieldValues',
-               FIELD2 => 'CustomField',
-           );
-           $self->Limit(
-               LEFTJOIN        => $CFvs,
-               FIELD           => 'Name',
-               QUOTEVALUE      => 0,
-               VALUE           => $TicketCFs . ".Content",
-               ENTRYAGGREGATOR => 'AND'
-           );
-
-           push @res, { %$row, ALIAS => $CFvs, FIELD => 'SortOrder' };
-           push @res, { %$row, ALIAS => $TicketCFs, FIELD => 'Content' };
+           my ($queue, $field, $cf, $column) = $self->_CustomFieldDecipher( $subkey );
+           push @res, $self->_OrderByCF( $row, ($cf || "$queue.$field") );
        } elsif ( $field eq "Custom" && $subkey eq "Ownership") {
            # PAW logic is "reversed"
            my $order = "ASC";

commit 604a8ec5141fd88348b63ae4dc52afdc8bdfcfa3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 18:34:14 2013 -0400

    SearchBuilder forces QUOTEVALUE => 0 for IS and IS NOT limits
    
    Remove the unnecessary (and in the first hunk, misleading) QUOTEVALUEs.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 3b2d637..38fdbb4 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -134,7 +134,6 @@ sub _OrderByCF {
         FIELD      => 'Name',
         OPERATOR   => 'IS NOT',
         VALUE      => 'NULL',
-        QUOTEVALUE => 1,
         ENTRYAGGREGATOR => 'AND',
     ) if $CFs;
     unless (blessed($cf)) {
@@ -500,7 +499,6 @@ sub _LimitCustomField {
             FIELD      => 'Name',
             OPERATOR   => 'IS NOT',
             VALUE      => 'NULL',
-            QUOTEVALUE => 0,
             ENTRYAGGREGATOR => 'AND',
             SUBCLAUSE  => $args{SUBCLAUSE},
         ) if $CFs;
@@ -715,7 +713,6 @@ sub _LimitCustomField {
                 FIELD           => 'Name',
                 OPERATOR        => 'IS NOT',
                 VALUE           => 'NULL',
-                QUOTEVALUE      => 0,
                 ENTRYAGGREGATOR => 'AND',
                 SUBCLAUSE       => $args{SUBCLAUSE},
             ) if $CFs;
@@ -727,7 +724,6 @@ sub _LimitCustomField {
                     FIELD           => $column || 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
-                    QUOTEVALUE      => 0,
                     ENTRYAGGREGATOR => 'OR',
                     SUBCLAUSE       => $args{SUBCLAUSE},
                 );
@@ -771,7 +767,6 @@ sub _LimitCustomField {
             FIELD      => 'id',
             OPERATOR   => 'IS',
             VALUE      => 'NULL',
-            QUOTEVALUE => 0,
         );
     }
 }

commit bcd79f3afc85d8976a65b94c5de9c158b6d1962f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 19:06:28 2013 -0400

    Remove uses of ::Unique; our SELECT DISTINCT is sufficient
    
    Rather than import a mixin (twice, in Tickets' case (?!)) which
    unique-ifies the results, rely on our SELECT DISCINCT machinery.  The
    original code was added when DistinctQuery was unimplemented for some
    database handles.

diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
index 48e571d..6834751 100644
--- a/lib/RT/CustomFields.pm
+++ b/lib/RT/CustomFields.pm
@@ -70,8 +70,6 @@ use warnings;
 
 use base 'RT::SearchBuilder';
 
-use DBIx::SearchBuilder::Unique;
-
 use RT::CustomField;
 
 sub Table { 'CustomFields'}
diff --git a/lib/RT/Report/Tickets.pm b/lib/RT/Report/Tickets.pm
index 3d3336c..afd79e2 100644
--- a/lib/RT/Report/Tickets.pm
+++ b/lib/RT/Report/Tickets.pm
@@ -275,15 +275,6 @@ sub _FieldToFunction {
     return %args;
 }
 
-
-# Override the AddRecord from DBI::SearchBuilder::Unique. id isn't id here
-# wedon't want to disambiguate all the items with a count of 1.
-sub AddRecord {
-    my $self = shift;
-    my $record = shift;
-    push @{$self->{'items'}}, $record;
-}
-
 1;
 
 
diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 38fdbb4..1f6e01b 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -136,16 +136,6 @@ sub _OrderByCF {
         VALUE      => 'NULL',
         ENTRYAGGREGATOR => 'AND',
     ) if $CFs;
-    unless (blessed($cf)) {
-        # For those cases where we are doing a join against the
-        # CF name, and don't have a CFid, use Unique to make sure
-        # we don't show duplicate tickets.  NOTE: I'm pretty sure
-        # this will stay mixed in for the life of the
-        # class/package, and not just for the life of the object.
-        # Potential performance issue.
-        require DBIx::SearchBuilder::Unique;
-        DBIx::SearchBuilder::Unique->import;
-    }
     my $CFvs = $self->Join(
         TYPE   => 'LEFT',
         ALIAS1 => $ocfvs,
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 9d3abf1..1dcccfb 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -94,7 +94,6 @@ use RT::SQL;
 sub Table { 'Tickets'}
 
 use RT::CustomFields;
-use DBIx::SearchBuilder::Unique;
 
 # Configuration Tables:
 

commit 46f2e4c77aeb25aea99c249b1f7465fb033c72d9
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 16:07:40 2013 -0400

    Ensure that ENTRYAGGRATOR does not mistakenly OR the ordering limit with some other limit

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 1f6e01b..13afc88 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -135,6 +135,7 @@ sub _OrderByCF {
         OPERATOR   => 'IS NOT',
         VALUE      => 'NULL',
         ENTRYAGGREGATOR => 'AND',
+        SUBCLAUSE  => ".ordering",
     ) if $CFs;
     my $CFvs = $self->Join(
         TYPE   => 'LEFT',

commit cfbb86257843a8844f7d318a5ec72966f9e42cdf
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 22:44:00 2013 -0400

    Update style of test file, and ensure that tests run in prefictable order

diff --git a/t/ticket/search_by_cf_freeform_multiple.t b/t/ticket/search_by_cf_freeform_multiple.t
index 1b4f092..61ec426 100644
--- a/t/ticket/search_by_cf_freeform_multiple.t
+++ b/t/ticket/search_by_cf_freeform_multiple.t
@@ -2,8 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test nodata => 1, tests => 118;
-use RT::Ticket;
+use RT::Test nodata => 1, tests => undef;
 
 my $q = RT::Test->load_or_create_queue( Name => 'Regression' );
 ok $q && $q->id, 'loaded or created queue';
@@ -22,42 +21,19 @@ my ($cf_name, $cf_id, $cf) = ("Test", 0, undef);
     $cf_id = $cf->id;
 }
 
-my ($total, @data, @tickets, %test) = (0, ());
-
-sub run_tests {
-    my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
-    foreach my $key ( sort keys %test ) {
-        my $tix = RT::Tickets->new(RT->SystemUser);
-        $tix->FromSQL( "( $query_prefix ) AND ( $key )" );
-
-        my $error = 0;
-
-        my $count = 0;
-        $count++ foreach grep $_, values %{ $test{$key} };
-        is($tix->Count, $count, "found correct number of ticket(s) by '$key'") or $error = 1;
-
-        my $good_tickets = ($tix->Count == $count);
-        while ( my $ticket = $tix->Next ) {
-            next if $test{$key}->{ $ticket->Subject };
-            diag $ticket->Subject ." ticket has been found when it's not expected";
-            $good_tickets = 0;
-        }
-        ok( $good_tickets, "all tickets are good with '$key'" ) or $error = 1;
-
-        diag "Wrong SQL query for '$key':". $tix->BuildSelectQuery if $error;
-    }
-}
+subtest "Creating tickets" => sub {
+    RT::Test->create_tickets( { Queue => $q->id },
+        { Subject => '-' },
+        { Subject => 'x', "CustomField-$cf_id" => 'x', },
+        { Subject => 'y', "CustomField-$cf_id" => 'y', },
+        { Subject => 'z', "CustomField-$cf_id" => 'z', },
+        { Subject => 'xy', "CustomField-$cf_id" => [ 'x', 'y' ], },
+        { Subject => 'xz', "CustomField-$cf_id" => [ 'x', 'z' ], },
+        { Subject => 'yz', "CustomField-$cf_id" => [ 'y', 'z' ], },
+    );
+};
 
- at data = (
-    { Subject => '-' },
-    { Subject => 'x', "CustomField-$cf_id" => 'x', },
-    { Subject => 'y', "CustomField-$cf_id" => 'y', },
-    { Subject => 'z', "CustomField-$cf_id" => 'z', },
-    { Subject => 'xy', "CustomField-$cf_id" => [ 'x', 'y' ], },
-    { Subject => 'xz', "CustomField-$cf_id" => [ 'x', 'z' ], },
-    { Subject => 'yz', "CustomField-$cf_id" => [ 'y', 'z' ], },
-);
-%test = (
+my @tests = (
     "CF.{$cf_id} IS NULL"                 => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
     "'CF.{$cf_name}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
     "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
@@ -113,13 +89,36 @@ sub run_tests {
     "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
     "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
 );
- at tickets = RT::Test->create_tickets( { Queue => $q->id }, @data);
-$total = scalar @tickets;
+run_tests(@tests);
 
-{
-    my $tix = RT::Tickets->new(RT->SystemUser);
-    $tix->FromSQL("Queue = '$queue'");
-    is($tix->Count, $total, "found $total tickets");
+
+sub run_tests {
+    my @tests = @_;
+    while (@tests) {
+        my $query = shift @tests;
+        my %results = %{ shift @tests };
+        subtest $query => sub {
+            my $tix = RT::Tickets->new(RT->SystemUser);
+            $tix->FromSQL( "$query" );
+
+            my $error = 0;
+
+            my $count = 0;
+            $count++ foreach grep $_, values %results;
+            is($tix->Count, $count, "found correct number of ticket(s)") or $error = 1;
+
+            my $good_tickets = ($tix->Count == $count);
+            while ( my $ticket = $tix->Next ) {
+                next if $results{ $ticket->Subject };
+                diag $ticket->Subject ." ticket has been found when it's not expected";
+                $good_tickets = 0;
+            }
+            ok( $good_tickets, "all tickets are good" ) or $error = 1;
+
+            diag "Wrong SQL: ". $tix->BuildSelectQuery if $error;
+        };
+    }
 }
-run_tests();
 
+
+done_testing;
diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index f8462a9..733df15 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -2,8 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test nodata => 1, tests => 106;
-use RT::Ticket;
+use RT::Test nodata => 1, tests => undef;
 
 my $q = RT::Test->load_or_create_queue( Name => 'Regression' );
 ok $q && $q->id, 'loaded or created queue';
@@ -22,39 +21,19 @@ my ($cf_name, $cf_id, $cf) = ("Test", 0, undef);
     $cf_id = $cf->id;
 }
 
-my ($total, @data, @tickets, %test) = (0, ());
+my $other_q = RT::Test->load_or_create_queue( Name => 'Other' );
+ok $other_q && $other_q->id, 'loaded or created queue';
 
-sub run_tests {
-    my $query_prefix = join ' OR ', map 'id = '. $_->id, @tickets;
-    foreach my $key ( sort keys %test ) {
-        my $tix = RT::Tickets->new(RT->SystemUser);
-        $tix->FromSQL( "( $query_prefix ) AND ( $key )" );
-
-        my $error = 0;
-
-        my $count = 0;
-        $count++ foreach grep $_, values %{ $test{$key} };
-        is($tix->Count, $count, "found correct number of ticket(s) by '$key'") or $error = 1;
-
-        my $good_tickets = ($tix->Count == $count);
-        while ( my $ticket = $tix->Next ) {
-            next if $test{$key}->{ $ticket->Subject };
-            diag $ticket->Subject ." ticket has been found when it's not expected";
-            $good_tickets = 0;
-        }
-        ok( $good_tickets, "all tickets are good with '$key'" ) or $error = 1;
-
-        diag "Wrong SQL query for '$key':". $tix->BuildSelectQuery if $error;
-    }
-}
+subtest "Creating tickets" => sub {
+    RT::Test->create_tickets( { Queue => $q->id },
+        { Subject => '-' },
+        { Subject => 'x', "CustomField-$cf_id" => 'x', },
+        { Subject => 'y', "CustomField-$cf_id" => 'y', },
+        { Subject => 'z', "CustomField-$cf_id" => 'z', },
+    );
+};
 
- at data = (
-    { Subject => '-' },
-    { Subject => 'x', "CustomField-$cf_id" => 'x', },
-    { Subject => 'y', "CustomField-$cf_id" => 'y', },
-    { Subject => 'z', "CustomField-$cf_id" => 'z', },
-);
-%test = (
+my @tests = (
     "CF.{$cf_id} IS NULL"                 => { '-' => 1, x => 0, y => 0, z => 0 },
     "'CF.{$cf_name}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0 },
     "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, x => 0, y => 0, z => 0 },
@@ -109,16 +88,37 @@ sub run_tests {
     "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 1, z => 1 },
     "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1 },
     "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1 },
-
 );
- at tickets = RT::Test->create_tickets( { Queue => $q->id }, @data);
-$total = scalar @tickets;
-{
-    my $tix = RT::Tickets->new(RT->SystemUser);
-    $tix->FromSQL("Queue = '$queue'");
-    is($tix->Count, $total, "found $total tickets");
+run_tests(@tests);
+
+
+sub run_tests {
+    my @tests = @_;
+    while (@tests) {
+        my $query = shift @tests;
+        my %results = %{ shift @tests };
+        subtest $query => sub {
+            my $tix = RT::Tickets->new(RT->SystemUser);
+            $tix->FromSQL( "$query" );
+
+            my $error = 0;
+
+            my $count = 0;
+            $count++ foreach grep $_, values %results;
+            is($tix->Count, $count, "found correct number of ticket(s)") or $error = 1;
+
+            my $good_tickets = ($tix->Count == $count);
+            while ( my $ticket = $tix->Next ) {
+                next if $results{ $ticket->Subject };
+                diag $ticket->Subject ." ticket has been found when it's not expected";
+                $good_tickets = 0;
+            }
+            ok( $good_tickets, "all tickets are good" ) or $error = 1;
+
+            diag "Wrong SQL: ". $tix->BuildSelectQuery if $error;
+        };
+    }
 }
-run_tests();
 
- at tickets = ();
 
+done_testing;

commit 266a8343511848dc464ba13dda8350f3fcd8df53
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 22:52:34 2013 -0400

    Fix != conditions with an explicit column provided
    
    Previously, the check for "or allow a row which has no value" failed to
    apply if an explicit column was provided.  Refactor the parenthization
    slightly, and allow the possibility of no-value even in the case of
    searching through particular columns.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 13afc88..40db514 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -555,8 +555,6 @@ sub _LimitCustomField {
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
         $self->_OpenParen( $args{SUBCLAUSE} );
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        $self->_OpenParen( $args{SUBCLAUSE} );
         # if column is defined then deal only with it
         # otherwise search in Content and in LargeContent
         if ( $column ) {
@@ -568,11 +566,10 @@ sub _LimitCustomField {
                 VALUE      => $value,
                 CASESENSITIVE => 0,
             ) );
-            $self->_CloseParen( $args{SUBCLAUSE} );
-            $self->_CloseParen( $args{SUBCLAUSE} );
-            $self->_CloseParen( $args{SUBCLAUSE} );
         }
         else {
+            $self->_OpenParen( $args{SUBCLAUSE} );
+            $self->_OpenParen( $args{SUBCLAUSE} );
             # need special treatment for Date
             if ( blessed($cf) and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
                 # no time specified, that means we want everything on a
@@ -708,20 +705,20 @@ sub _LimitCustomField {
                 SUBCLAUSE       => $args{SUBCLAUSE},
             ) if $CFs;
             $self->_CloseParen( $args{SUBCLAUSE} );
+        }
 
-            if ($negative_op) {
-                $self->Limit(
-                    ALIAS           => $ocfvalias,
-                    FIELD           => $column || 'Content',
-                    OPERATOR        => 'IS',
-                    VALUE           => 'NULL',
-                    ENTRYAGGREGATOR => 'OR',
-                    SUBCLAUSE       => $args{SUBCLAUSE},
-                );
-            }
-
-            $self->_CloseParen( $args{SUBCLAUSE} );
+        if ($negative_op and not $null_op) {
+            $self->Limit(
+                ALIAS           => $ocfvalias,
+                FIELD           => $column || 'Content',
+                OPERATOR        => 'IS',
+                VALUE           => 'NULL',
+                ENTRYAGGREGATOR => 'OR',
+                SUBCLAUSE       => $args{SUBCLAUSE},
+            );
         }
+        $self->_CloseParen( $args{SUBCLAUSE} );
+
     }
     else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index 733df15..3681c39 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -45,14 +45,22 @@ my @tests = (
     "'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1 },
 
     "CF.{$cf_id} = 'x'"                   => { '-' => 0, x => 1, y => 0, z => 0 },
+    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, x => 1, y => 0, z => 0 },
     "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0 },
     "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}.Content' = 'x'"  => { '-' => 0, x => 1, y => 0, z => 0 },
     "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}.Content' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0 },
 
     "CF.{$cf_id} != 'x'"                  => { '-' => 1, x => 0, y => 1, z => 1 },
+    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, x => 0, y => 1, z => 1 },
     "'CF.{$cf_name}' != 'x'"              => { '-' => 1, x => 0, y => 1, z => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, x => 0, y => 1, z => 1 },
     "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}.Content' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1 },
     "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}.Content' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1 },
 
     "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 1, y => 1, z => 0 },
     "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 1, y => 1, z => 0 },

commit bbb10d6bf864f7544b970d646066663417363642
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 22:56:16 2013 -0400

    Tests for tickets in alternate queues, without the CF applied
    
    A queue without the CF applied should act identically to a ticket with
    no value for that CF, in all cases.

diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index 3681c39..c465563 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -27,6 +27,7 @@ ok $other_q && $other_q->id, 'loaded or created queue';
 subtest "Creating tickets" => sub {
     RT::Test->create_tickets( { Queue => $q->id },
         { Subject => '-' },
+        { Subject => "other", Queue => $other_q->id },
         { Subject => 'x', "CustomField-$cf_id" => 'x', },
         { Subject => 'y', "CustomField-$cf_id" => 'y', },
         { Subject => 'z', "CustomField-$cf_id" => 'z', },
@@ -34,68 +35,68 @@ subtest "Creating tickets" => sub {
 };
 
 my @tests = (
-    "CF.{$cf_id} IS NULL"                 => { '-' => 1, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' IS NULL"      => { '-' => 1, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} IS NOT NULL"             => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' IS NOT NULL"         => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' IS NOT NULL"    => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1 },
-
-    "CF.{$cf_id} = 'x'"                   => { '-' => 0, x => 1, y => 0, z => 0 },
-    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}.Content' = 'x'"  => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}.Content' = 'x'" => { '-' => 0, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} != 'x'"                  => { '-' => 1, x => 0, y => 1, z => 1 },
-    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}' != 'x'"              => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}.Content' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}.Content' != 'x'" => { '-' => 1, x => 0, y => 1, z => 1 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 1, y => 1, z => 0 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 1, y => 1, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 1, y => 1, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 1, y => 1, z => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, x => 0, y => 0, z => 1 },
-    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, x => 0, y => 0, z => 1 },
-    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, x => 0, y => 0, z => 1 },
-    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, x => 0, y => 0, z => 1 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1 },
+    "CF.{$cf_id} IS NULL"                 => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}' IS NULL"             => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' IS NULL"      => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+
+    "CF.{$cf_id} IS NOT NULL"             => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}' IS NOT NULL"         => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}' IS NOT NULL"    => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+
+    "CF.{$cf_id} = 'x'"                   => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x'"               => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}.Content' = 'x'"  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}.Content' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+
+    "CF.{$cf_id} != 'x'"                  => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.{$cf_name}' != 'x'"              => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+
+    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
+    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
+    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
+    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
 );
 run_tests(@tests);
 

commit a031107c79d0814889ce56600a73da4571224022
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Apr 26 22:58:20 2013 -0400

    Condense IS NULL and IS NOT NULL logic
    
    There is no hint as to why with a column specified, null tests should
    act differently than without -- which the recently added tests bear out.
    Simplify the logic by removing the $column check.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 40db514..6e46fcb 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -472,16 +472,16 @@ sub _LimitCustomField {
 
     my $single_value = !blessed($cf) || $cf->SingleValue;
 
-    if ( $null_op && !$column ) {
-        # IS[ NOT] NULL without column is the same as has[ no] any CF value,
-        # we can reuse our default joins for this operation
-        # with column specified we have different situation
+    if ( $null_op ) {
+        # IS NULL is the same as lacks a CF value, and IS NOT NULL means
+        # has any value.  We can reuse our default joins for this
+        # operation.
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
         $self->_OpenParen( $args{SUBCLAUSE} );
         $self->Limit(
             %args,
             ALIAS    => $ocfvalias,
-            FIELD    => 'id',
+            FIELD    => ($column || 'id'),
             OPERATOR => $op,
             VALUE    => $value,
         );
@@ -707,7 +707,7 @@ sub _LimitCustomField {
             $self->_CloseParen( $args{SUBCLAUSE} );
         }
 
-        if ($negative_op and not $null_op) {
+        if ($negative_op) {
             $self->Limit(
                 ALIAS           => $ocfvalias,
                 FIELD           => $column || 'Content',
diff --git a/t/ticket/search_by_cf_freeform_multiple.t b/t/ticket/search_by_cf_freeform_multiple.t
index 61ec426..4772beb 100644
--- a/t/ticket/search_by_cf_freeform_multiple.t
+++ b/t/ticket/search_by_cf_freeform_multiple.t
@@ -34,23 +34,39 @@ subtest "Creating tickets" => sub {
 };
 
 my @tests = (
-    "CF.{$cf_id} IS NULL"                 => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_name}' IS NULL"      => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-
-    "CF.{$cf_id} IS NOT NULL"             => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}' IS NOT NULL"         => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_id}' IS NOT NULL"    => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "CF.{$cf_id} IS NULL"                      => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "CF.{$cf_id}.Content IS NULL"              => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "CF.{$cf_id}.LargeContent IS NULL"         => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "'CF.{$cf_name}' IS NULL"                  => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "'CF.{$cf_name}.Content' IS NULL"          => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "'CF.{$cf_name}.LargeContent' IS NULL"     => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "'CF.$queue.{$cf_id}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "'CF.$queue.{$cf_name}' IS NULL"           => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+
+    "CF.{$cf_id} IS NOT NULL"                  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "CF.{$cf_id}.Content IS NOT NULL"          => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "CF.{$cf_id}.LargeContent IS NOT NULL"     => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "'CF.{$cf_name}' IS NOT NULL"              => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "'CF.{$cf_name}.Content' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "'CF.{$cf_name}.LargeContent' IS NOT NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
+    "'CF.$queue.{$cf_id}' IS NOT NULL"         => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "'CF.$queue.{$cf_name}' IS NOT NULL"       => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
 
     "CF.{$cf_id} = 'x'"                   => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
+    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
+    "CF.{$cf_id}.LargeContent = 'x'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
     "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
+    "'CF.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
     "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
     "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
 
     "CF.{$cf_id} != 'x'"                  => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
+    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
+    "CF.{$cf_id}.LargeContent != 'x'"     => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
     "'CF.{$cf_name}' != 'x'"              => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
+    "'CF.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
     "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
     "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
 
diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index c465563..74fd057 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -35,33 +35,49 @@ subtest "Creating tickets" => sub {
 };
 
 my @tests = (
-    "CF.{$cf_id} IS NULL"                 => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' IS NULL"             => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' IS NULL"        => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' IS NULL"      => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} IS NOT NULL"             => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' IS NOT NULL"         => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' IS NOT NULL"    => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-
-    "CF.{$cf_id} = 'x'"                   => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x'"               => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}.Content' = 'x'"  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}.Content' = 'x'" => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} != 'x'"                  => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}' != 'x'"              => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}.Content' != 'x'" => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "CF.{$cf_id}.Content IS NULL"                => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "CF.{$cf_id}.LargeContent IS NULL"           => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}' IS NULL"                    => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}.Content' IS NULL"            => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}.LargeContent' IS NULL"       => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}' IS NULL"               => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' IS NULL"             => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
+
+    "CF.{$cf_id} IS NOT NULL"                    => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "CF.{$cf_id}.Content IS NOT NULL"            => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "CF.{$cf_id}.LargeContent IS NOT NULL"       => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}.Content' IS NOT NULL"        => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}.LargeContent' IS NOT NULL"   => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' IS NOT NULL"           => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}' IS NOT NULL"         => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+
+    "CF.{$cf_id} = 'x'"                          => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "CF.{$cf_id}.Content = 'x'"                  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "CF.{$cf_id}.LargeContent = 'x'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.{$cf_name}' = 'x'"                      => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"              => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.{$cf_name}.LargeContent' = 'x'"         => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}' = 'x'"                 => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}.Content' = 'x'"         => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_id}.LargeContent' = 'x'"    => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}' = 'x'"               => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}.Content' = 'x'"       => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
+    "'CF.$queue.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
+
+    "CF.{$cf_id} != 'x'"                         => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "CF.{$cf_id}.Content != 'x'"                 => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "CF.{$cf_id}.LargeContent != 'x'"            => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
+    "'CF.{$cf_name}' != 'x'"                     => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"             => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.{$cf_name}.LargeContent' != 'x'"        => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}' != 'x'"                => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}.Content' != 'x'"        => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_id}.LargeContent' != 'x'"   => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}' != 'x'"              => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}.Content' != 'x'"      => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
+    "'CF.$queue.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
 
     "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
     "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },

commit 4f04243ba702fb0e347eb6152574db669aea6bc8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:17:22 2013 -0400

    Factor out blessed($cf) check

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 6e46fcb..bc08581 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -406,68 +406,61 @@ sub _LimitCustomField {
         return %args;
     };
 
-    if ( blessed($cf) && $cf->Type eq 'IPAddress' ) {
-        my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
-        if ($parsed) {
-            $value = $parsed;
-        }
-        else {
-            $RT::Logger->warn("$value is not a valid IPAddress");
+    ########## Content pre-parsing if we know things about the CF
+    if ( blessed($cf) ) {
+        if ( $cf->Type eq 'IPAddress' ) {
+            my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
+            if ($parsed) {
+                $value = $parsed;
+            } else {
+                $RT::Logger->warn("$value is not a valid IPAddress");
+            }
         }
-    }
-
-    if ( blessed($cf) && $cf->Type eq 'IPAddressRange' ) {
 
-        if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
-
-            # convert incomplete 192.168/24 to 192.168.0.0/24 format
-            $value =
-              join( '.', map $_ || 0, ( split /\./, $1 )[ 0 .. 3 ] ) . "/$2"
-              || $value;
-        }
+        if ( $cf->Type eq 'IPAddressRange' ) {
+            if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
+                # convert incomplete 192.168/24 to 192.168.0.0/24 format
+                $value =
+                  join( '.', map $_ || 0, ( split /\./, $1 )[ 0 .. 3 ] ) . "/$2"
+                  || $value;
+            }
 
-        my ( $start_ip, $end_ip ) =
-          RT::ObjectCustomFieldValue->ParseIPRange($value);
-        if ( $start_ip && $end_ip ) {
-            if ( $op =~ /^([<>])=?$/ ) {
-                my $is_less = $1 eq '<' ? 1 : 0;
-                if ( $is_less ) {
+            my ( $start_ip, $end_ip ) =
+              RT::ObjectCustomFieldValue->ParseIPRange($value);
+            if ( $start_ip && $end_ip ) {
+                if ( $op =~ /^<=?$/ ) {
                     $value = $start_ip;
-                }
-                else {
+                } elsif ($op =~ /^>=?$/ ) {
                     $value = $end_ip;
+                } else {
+                    $value = join '-', $start_ip, $end_ip;
                 }
-            }
-            else {
-                $value = join '-', $start_ip, $end_ip;
+            } else {
+                $RT::Logger->warn("$value is not a valid IPAddressRange");
             }
         }
-        else {
-            $RT::Logger->warn("$value is not a valid IPAddressRange");
-        }
-    }
 
-    if ( blessed($cf) && $cf->Type =~ /^Date(?:Time)?$/ ) {
-        my $date = RT::Date->new( $self->CurrentUser );
-        $date->Set( Format => 'unknown', Value => $value );
-        if ( $date->Unix ) {
-
-            if (
-                   $cf->Type eq 'Date'
-                || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i
-                || (   $value !~ /midnight|\d+:\d+:\d+/i
-                    && $date->Time( Timezone => 'user' ) eq '00:00:00' )
-              )
-            {
-                $value = $date->Date( Timezone => 'user' );
-            }
-            else {
-                $value = $date->DateTime;
+        if ( $cf->Type =~ /^Date(?:Time)?$/ ) {
+            my $date = RT::Date->new( $self->CurrentUser );
+            $date->Set( Format => 'unknown', Value => $value );
+            if ( $date->Unix ) {
+                if (
+                       $cf->Type eq 'Date'
+                           # Heuristics to determine if a date, and not
+                           # a datetime, was entered:
+                    || $value =~ /^\s*(?:today|tomorrow|yesterday)\s*$/i
+                    || (   $value !~ /midnight|\d+:\d+:\d+/i
+                        && $date->Time( Timezone => 'user' ) eq '00:00:00' )
+                  )
+                {
+                    $value = $date->Date( Timezone => 'user' );
+                } else {
+                    $value = $date->DateTime;
+                }
+            } else {
+                $RT::Logger->warn("$value is not a valid date string");
             }
         }
-        else {
-            $RT::Logger->warn("$value is not a valid date string");
-        }
     }
 
     my $single_value = !blessed($cf) || $cf->SingleValue;

commit d77d9074c45fd4597c3caffb105f5ffebd6451b8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:20:37 2013 -0400

    Move IPAddressRange recursive call earlier

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index bc08581..ac13c7c 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -438,6 +438,50 @@ sub _LimitCustomField {
             } else {
                 $RT::Logger->warn("$value is not a valid IPAddressRange");
             }
+
+            # Recurse if they want a range comparison
+            if ( $op !~ /^[<>]=?$/ ) {
+                my ($start_ip, $end_ip) = split /-/, $value;
+                $self->_OpenParen( $args{SUBCLAUSE} );
+                # Ideally we would limit >= 000.000.000.000 and <=
+                # 255.255.255.255 so DB optimizers could use better
+                # estimations and scan less rows, but this breaks with IPv6.
+                if ( $op !~ /NOT|!=|<>/i ) { # positive equation
+                    $self->_LimitCustomField(
+                        %args,
+                        OPERATOR    => '<=',
+                        VALUE       => $end_ip,
+                        CUSTOMFIELD => $cf,
+                        COLUMN      => 'Content',
+                    );
+                    $self->_LimitCustomField(
+                        %args,
+                        OPERATOR    => '>=',
+                        VALUE       => $start_ip,
+                        CUSTOMFIELD => $cf,
+                        COLUMN      => 'LargeContent',
+                        ENTRYAGGREGATOR => 'AND',
+                    );
+                } else { # negative equation
+                    $self->_LimitCustomField(
+                        %args,
+                        OPERATOR    => '>',
+                        VALUE       => $end_ip,
+                        CUSTOMFIELD => $cf,
+                        COLUMN      => 'Content',
+                    );
+                    $self->_LimitCustomField(
+                        %args,
+                        OPERATOR    => '<',
+                        VALUE       => $start_ip,
+                        CUSTOMFIELD => $cf,
+                        COLUMN      => 'LargeContent',
+                        ENTRYAGGREGATOR => 'OR',
+                    );
+                }
+                $self->_CloseParen( $args{SUBCLAUSE} );
+                return;
+            }
         }
 
         if ( $cf->Type =~ /^Date(?:Time)?$/ ) {
@@ -488,61 +532,6 @@ sub _LimitCustomField {
         ) if $CFs;
         $self->_CloseParen( $args{SUBCLAUSE} );
     }
-    elsif ( $op !~ /^[<>]=?$/ && (  blessed($cf) && $cf->Type eq 'IPAddressRange')) {
-        my ($start_ip, $end_ip) = split /-/, $value;
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        if ( $op !~ /NOT|!=|<>/i ) { # positive equation
-            $self->_LimitCustomField(
-                %args,
-                OPERATOR    => '<=',
-                VALUE       => $end_ip,
-                CUSTOMFIELD => $cf,
-                COLUMN      => 'Content',
-            );
-            $self->_LimitCustomField(
-                %args,
-                OPERATOR    => '>=',
-                VALUE       => $start_ip,
-                CUSTOMFIELD => $cf,
-                COLUMN      => 'LargeContent',
-                ENTRYAGGREGATOR => 'AND',
-            );
-            # as well limit borders so DB optimizers can use better
-            # estimations and scan less rows
-# have to disable this tweak because of ipv6
-#            $self->_CustomFieldLimit(
-#                $field, '>=', '000.000.000.000', %rest,
-#                SUBKEY          => $rest{'SUBKEY'}. '.Content',
-#                ENTRYAGGREGATOR => 'AND',
-#            );
-#            $self->_CustomFieldLimit(
-#                $field, '<=', '255.255.255.255', %rest,
-#                SUBKEY          => $rest{'SUBKEY'}. '.LargeContent',
-#                ENTRYAGGREGATOR => 'AND',
-#            );  
-        }       
-        else { # negative equation
-            $self->_LimitCustomField(
-                %args,
-                OPERATOR    => '>',
-                VALUE       => $end_ip,
-                CUSTOMFIELD => $cf,
-                COLUMN      => 'Content',
-            );
-            $self->_LimitCustomField(
-                %args,
-                OPERATOR    => '<',
-                VALUE       => $start_ip,
-                CUSTOMFIELD => $cf,
-                COLUMN      => 'LargeContent',
-                ENTRYAGGREGATOR => 'OR',
-            );
-            # TODO: as well limit borders so DB optimizers can use better
-            # estimations and scan less rows, but it's harder to do
-            # as we have OR aggregator
-        }
-        $self->_CloseParen( $args{SUBCLAUSE} );
-    } 
     elsif ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );

commit db0d1208adb8a4c779c8306f81cac603e779671d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:23:32 2013 -0400

    Move Date range check for DateTimes into a recursive call, earlier

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index ac13c7c..6127d41 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -504,6 +504,36 @@ sub _LimitCustomField {
             } else {
                 $RT::Logger->warn("$value is not a valid date string");
             }
+
+            # Recurse if day equality is being checked on a datetime
+            if ( $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
+                my $date = RT::Date->new( $self->CurrentUser );
+                $date->Set( Format => 'unknown', Value => $value );
+                my $daystart = $date->ISO;
+                $date->AddDay;
+                my $dayend = $date->ISO;
+
+                $self->_OpenParen( $args{SUBCLAUSE} );
+                $self->_LimitCustomField(
+                    %args,
+                    OPERATOR        => ">=",
+                    VALUE           => $daystart,
+                    CUSTOMFIELD     => $cf,
+                    COLUMN          => 'Content',
+                    ENTRYAGGREGATOR => 'AND',
+                );
+
+                $self->_LimitCustomField(
+                    %args,
+                    OPERATOR        => "<",
+                    VALUE           => $dayend,
+                    CUSTOMFIELD     => $cf,
+                    COLUMN          => 'Content',
+                    ENTRYAGGREGATOR => 'AND',
+                );
+                $self->_CloseParen( $args{SUBCLAUSE} );
+                return;
+            }
         }
     }
 
@@ -552,39 +582,7 @@ sub _LimitCustomField {
         else {
             $self->_OpenParen( $args{SUBCLAUSE} );
             $self->_OpenParen( $args{SUBCLAUSE} );
-            # need special treatment for Date
-            if ( blessed($cf) and $cf->Type eq 'DateTime' and $op eq '=' && $value !~ /:/ ) {
-                # no time specified, that means we want everything on a
-                # particular day.  in the database, we need to check for >
-                # and < the edges of that day.
-                my $date = RT::Date->new( $self->CurrentUser );
-                $date->Set( Format => 'unknown', Value => $value );
-                my $daystart = $date->ISO;
-                $date->AddDay;
-                my $dayend = $date->ISO;
-
-                $self->_OpenParen( $args{SUBCLAUSE} );
-
-                $self->Limit(
-                    %args,
-                    ALIAS    => $ocfvalias,
-                    FIELD    => 'Content',
-                    OPERATOR => ">=",
-                    VALUE    => $daystart,
-                );
-
-                $self->Limit(
-                    %args,
-                    ALIAS    => $ocfvalias,
-                    FIELD    => 'Content',
-                    OPERATOR => "<",
-                    VALUE    => $dayend,
-                    ENTRYAGGREGATOR => 'AND',
-                );
-
-                $self->_CloseParen( $args{SUBCLAUSE} );
-            }
-            elsif ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
+            if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
                 if ( length( Encode::encode_utf8($value) ) < 256 ) {
                     $self->Limit(
                         %args,

commit 6b1e38d92be093acb36b91a931fcc33a44d8e34b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:24:35 2013 -0400

    Remove a mis-placed comment

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 6127d41..d73186d 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -379,11 +379,6 @@ sub _LimitCustomField {
 
     $args{SUBCLAUSE} ||= "cf-$cfkey";
 
-# If we're trying to find custom fields that don't match something, we
-# want tickets where the custom field has no value at all.  Note that
-# we explicitly don't include the "IS NULL" case, since we would
-# otherwise end up with a redundant clause.
-
     my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
     my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
 
@@ -392,7 +387,7 @@ sub _LimitCustomField {
 
         my %args = @_;
         return %args unless $args{'FIELD'} eq 'LargeContent';
-        
+
         my $op = $args{'OPERATOR'};
         if ( $op eq '=' ) {
             $args{'OPERATOR'} = 'MATCHES';

commit d807a56d84dde2c3f6930e3a65d0c9be077d186a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:31:26 2013 -0400

    Short-circuit the NULL tests

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index d73186d..8d61f27 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -380,7 +380,6 @@ sub _LimitCustomField {
     $args{SUBCLAUSE} ||= "cf-$cfkey";
 
     my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
-    my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
 
     my $fix_op = sub {
         return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
@@ -534,10 +533,9 @@ sub _LimitCustomField {
 
     my $single_value = !blessed($cf) || $cf->SingleValue;
 
-    if ( $null_op ) {
-        # IS NULL is the same as lacks a CF value, and IS NOT NULL means
-        # has any value.  We can reuse our default joins for this
-        # operation.
+    ########## Limits
+    # IS NULL and IS NOT NULL checks
+    if ( $op =~ /^IS( NOT)?$/i ) {
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
         $self->_OpenParen( $args{SUBCLAUSE} );
         $self->Limit(
@@ -547,6 +545,7 @@ sub _LimitCustomField {
             OPERATOR => $op,
             VALUE    => $value,
         );
+        # See below for an explanation of this limit
         $self->Limit(
             ALIAS      => $CFs,
             FIELD      => 'Name',
@@ -556,8 +555,9 @@ sub _LimitCustomField {
             SUBCLAUSE  => $args{SUBCLAUSE},
         ) if $CFs;
         $self->_CloseParen( $args{SUBCLAUSE} );
+        return;
     }
-    elsif ( !$negative_op || $single_value ) {
+    if ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 

commit 4ddd202b48ec75ec6f79b4f89d1301eef80d5750
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:34:06 2013 -0400

    Collect helper variables closer to where they are used

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 8d61f27..ec36604 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -379,7 +379,6 @@ sub _LimitCustomField {
 
     $args{SUBCLAUSE} ||= "cf-$cfkey";
 
-    my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
 
     my $fix_op = sub {
         return @_ unless RT->Config->Get('DatabaseType') eq 'Oracle';
@@ -531,8 +530,6 @@ sub _LimitCustomField {
         }
     }
 
-    my $single_value = !blessed($cf) || $cf->SingleValue;
-
     ########## Limits
     # IS NULL and IS NOT NULL checks
     if ( $op =~ /^IS( NOT)?$/i ) {
@@ -557,6 +554,10 @@ sub _LimitCustomField {
         $self->_CloseParen( $args{SUBCLAUSE} );
         return;
     }
+
+    my $single_value = !blessed($cf) || $cf->SingleValue;
+    my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
+
     if ( !$negative_op || $single_value ) {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );

commit f160a44c0f0b16178529dc612d471b665c6eafcb
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:00:11 2013 -0400

    Apply Demorgan's to make the less common case clearer

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index ec36604..8e6adc5 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -558,7 +558,45 @@ sub _LimitCustomField {
     my $single_value = !blessed($cf) || $cf->SingleValue;
     my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
 
-    if ( !$negative_op || $single_value ) {
+    # A negative limit on a multi-value CF means _none_ of the values
+    # are the given value
+    if ( $negative_op and not $single_value ) {
+        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
+        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
+
+        # Reverse the limit we apply to the join, and check IS NULL
+        $op =~ s/!|NOT\s+//i;
+
+        # if column is defined then deal only with it
+        # otherwise search in Content and in LargeContent
+        if ( $column ) {
+            $self->Limit( $fix_op->(
+                LEFTJOIN   => $ocfvalias,
+                ALIAS      => $ocfvalias,
+                FIELD      => $column,
+                OPERATOR   => $op,
+                VALUE      => $value,
+                CASESENSITIVE => 0,
+            ) );
+        }
+        else {
+            $self->Limit(
+                LEFTJOIN   => $ocfvalias,
+                ALIAS      => $ocfvalias,
+                FIELD      => 'Content',
+                OPERATOR   => $op,
+                VALUE      => $value,
+                CASESENSITIVE => 0,
+            );
+        }
+        $self->Limit(
+            %args,
+            ALIAS      => $ocfvalias,
+            FIELD      => 'id',
+            OPERATOR   => 'IS',
+            VALUE      => 'NULL',
+        );
+    } else {
         $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
         my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
@@ -696,43 +734,6 @@ sub _LimitCustomField {
         $self->_CloseParen( $args{SUBCLAUSE} );
 
     }
-    else {
-        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
-
-        # reverse operation
-        $op =~ s/!|NOT\s+//i;
-
-        # if column is defined then deal only with it
-        # otherwise search in Content and in LargeContent
-        if ( $column ) {
-            $self->Limit( $fix_op->(
-                LEFTJOIN   => $ocfvalias,
-                ALIAS      => $ocfvalias,
-                FIELD      => $column,
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            ) );
-        }
-        else {
-            $self->Limit(
-                LEFTJOIN   => $ocfvalias,
-                ALIAS      => $ocfvalias,
-                FIELD      => 'Content',
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            );
-        }
-        $self->Limit(
-            %args,
-            ALIAS      => $ocfvalias,
-            FIELD      => 'id',
-            OPERATOR   => 'IS',
-            VALUE      => 'NULL',
-        );
-    }
 }
 
 =head2 Limit PARAMHASH

commit f1da2423d4f8b43812733fd697701b730c70411a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:02:07 2013 -0400

    Fold two parallel statements into one, removing a misleading and incorrect comment

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 8e6adc5..0b8d85a 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -567,28 +567,14 @@ sub _LimitCustomField {
         # Reverse the limit we apply to the join, and check IS NULL
         $op =~ s/!|NOT\s+//i;
 
-        # if column is defined then deal only with it
-        # otherwise search in Content and in LargeContent
-        if ( $column ) {
-            $self->Limit( $fix_op->(
-                LEFTJOIN   => $ocfvalias,
-                ALIAS      => $ocfvalias,
-                FIELD      => $column,
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            ) );
-        }
-        else {
-            $self->Limit(
-                LEFTJOIN   => $ocfvalias,
-                ALIAS      => $ocfvalias,
-                FIELD      => 'Content',
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            );
-        }
+        $self->Limit( $fix_op->(
+            LEFTJOIN   => $ocfvalias,
+            ALIAS      => $ocfvalias,
+            FIELD      => ($column || 'Content'),
+            OPERATOR   => $op,
+            VALUE      => $value,
+            CASESENSITIVE => 0,
+        ) );
         $self->Limit(
             %args,
             ALIAS      => $ocfvalias,

commit 2e8894ebf4e0c5da7de0e3caa4a1150968a145c1
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 14:36:52 2013 -0400

    Factor out the common join to the OCFV table
    
    The two calls to _CustomFieldJoin can be moved out and into one call, as
    long as care is taken to preserve the logic as to when new indexes are
    necessary.  Specifically, new indexes are never necessary with
    single-value CFs, and are used if the operator is a negative operator OR
    is not /^[<>]=?$/ -- that is, if it is not a relative comparitor.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 0b8d85a..b78551f 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -558,12 +558,13 @@ sub _LimitCustomField {
     my $single_value = !blessed($cf) || $cf->SingleValue;
     my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
 
+    $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++
+        if not $single_value and $op =~ /^(!?=|(NOT )?LIKE)$/i;
+    my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
+
     # A negative limit on a multi-value CF means _none_ of the values
     # are the given value
     if ( $negative_op and not $single_value ) {
-        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
-
         # Reverse the limit we apply to the join, and check IS NULL
         $op =~ s/!|NOT\s+//i;
 
@@ -583,8 +584,6 @@ sub _LimitCustomField {
             VALUE      => 'NULL',
         );
     } else {
-        $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++ if not $single_value and not $op =~ /^[<>]=?$/;
-        my ($ocfvalias, $CFs) = $self->_CustomFieldJoin( $cfkey, $cf );
 
         $self->_OpenParen( $args{SUBCLAUSE} );
         # if column is defined then deal only with it

commit 4b85902e330ea22604b29f8fb51ebab3c95c5f08
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:12:41 2013 -0400

    Short-circuit from negative queries on multiple-value CFs

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index b78551f..7f3de96 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -583,66 +583,27 @@ sub _LimitCustomField {
             OPERATOR   => 'IS',
             VALUE      => 'NULL',
         );
-    } else {
+        return;
+    }
 
+    $self->_OpenParen( $args{SUBCLAUSE} );
+    # if column is defined then deal only with it
+    # otherwise search in Content and in LargeContent
+    if ( $column ) {
+        $self->Limit( $fix_op->(
+            %args,
+            ALIAS      => $ocfvalias,
+            FIELD      => $column,
+            OPERATOR   => $op,
+            VALUE      => $value,
+            CASESENSITIVE => 0,
+        ) );
+    }
+    else {
         $self->_OpenParen( $args{SUBCLAUSE} );
-        # if column is defined then deal only with it
-        # otherwise search in Content and in LargeContent
-        if ( $column ) {
-            $self->Limit( $fix_op->(
-                %args,
-                ALIAS      => $ocfvalias,
-                FIELD      => $column,
-                OPERATOR   => $op,
-                VALUE      => $value,
-                CASESENSITIVE => 0,
-            ) );
-        }
-        else {
-            $self->_OpenParen( $args{SUBCLAUSE} );
-            $self->_OpenParen( $args{SUBCLAUSE} );
-            if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
-                if ( length( Encode::encode_utf8($value) ) < 256 ) {
-                    $self->Limit(
-                        %args,
-                        ALIAS    => $ocfvalias,
-                        FIELD    => 'Content',
-                        OPERATOR => $op,
-                        VALUE    => $value,
-                        CASESENSITIVE => 0,
-                    );
-                }
-                else {
-                    $self->_OpenParen( $args{SUBCLAUSE} );
-                    $self->Limit(
-                        ALIAS           => $ocfvalias,
-                        FIELD           => 'Content',
-                        OPERATOR        => '=',
-                        VALUE           => '',
-                        ENTRYAGGREGATOR => 'OR',
-                        SUBCLAUSE       => $args{SUBCLAUSE},
-                    );
-                    $self->Limit(
-                        ALIAS           => $ocfvalias,
-                        FIELD           => 'Content',
-                        OPERATOR        => 'IS',
-                        VALUE           => 'NULL',
-                        ENTRYAGGREGATOR => 'OR',
-                        SUBCLAUSE       => $args{SUBCLAUSE},
-                    );
-                    $self->_CloseParen( $args{SUBCLAUSE} );
-                    $self->Limit( $fix_op->(
-                        ALIAS           => $ocfvalias,
-                        FIELD           => 'LargeContent',
-                        OPERATOR        => $op,
-                        VALUE           => $value,
-                        ENTRYAGGREGATOR => 'AND',
-                        SUBCLAUSE       => $args{SUBCLAUSE},
-                        CASESENSITIVE => 0,
-                    ) );
-                }
-            }
-            else {
+        $self->_OpenParen( $args{SUBCLAUSE} );
+        if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
+            if ( length( Encode::encode_utf8($value) ) < 256 ) {
                 $self->Limit(
                     %args,
                     ALIAS    => $ocfvalias,
@@ -651,24 +612,24 @@ sub _LimitCustomField {
                     VALUE    => $value,
                     CASESENSITIVE => 0,
                 );
-
-                $self->_OpenParen( $args{SUBCLAUSE} );
+            }
+            else {
                 $self->_OpenParen( $args{SUBCLAUSE} );
                 $self->Limit(
                     ALIAS           => $ocfvalias,
                     FIELD           => 'Content',
                     OPERATOR        => '=',
                     VALUE           => '',
+                    ENTRYAGGREGATOR => 'OR',
                     SUBCLAUSE       => $args{SUBCLAUSE},
-                    ENTRYAGGREGATOR => 'OR'
                 );
                 $self->Limit(
                     ALIAS           => $ocfvalias,
                     FIELD           => 'Content',
                     OPERATOR        => 'IS',
                     VALUE           => 'NULL',
+                    ENTRYAGGREGATOR => 'OR',
                     SUBCLAUSE       => $args{SUBCLAUSE},
-                    ENTRYAGGREGATOR => 'OR'
                 );
                 $self->_CloseParen( $args{SUBCLAUSE} );
                 $self->Limit( $fix_op->(
@@ -680,45 +641,83 @@ sub _LimitCustomField {
                     SUBCLAUSE       => $args{SUBCLAUSE},
                     CASESENSITIVE => 0,
                 ) );
-                $self->_CloseParen( $args{SUBCLAUSE} );
             }
-            $self->_CloseParen( $args{SUBCLAUSE} );
+        }
+        else {
+            $self->Limit(
+                %args,
+                ALIAS    => $ocfvalias,
+                FIELD    => 'Content',
+                OPERATOR => $op,
+                VALUE    => $value,
+                CASESENSITIVE => 0,
+            );
 
-            # XXX: if we join via CustomFields table then
-            # because of order of left joins we get NULLs in
-            # CF table and then get nulls for those records
-            # in OCFVs table what result in wrong results
-            # as decifer method now tries to load a CF then
-            # we fall into this situation only when there
-            # are more than one CF with the name in the DB.
-            # the same thing applies to order by call.
-            # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
-            # we want treat IS NULL as (not applies or has
-            # no value)
+            $self->_OpenParen( $args{SUBCLAUSE} );
+            $self->_OpenParen( $args{SUBCLAUSE} );
             $self->Limit(
-                ALIAS           => $CFs,
-                FIELD           => 'Name',
-                OPERATOR        => 'IS NOT',
-                VALUE           => 'NULL',
-                ENTRYAGGREGATOR => 'AND',
+                ALIAS           => $ocfvalias,
+                FIELD           => 'Content',
+                OPERATOR        => '=',
+                VALUE           => '',
                 SUBCLAUSE       => $args{SUBCLAUSE},
-            ) if $CFs;
-            $self->_CloseParen( $args{SUBCLAUSE} );
-        }
-
-        if ($negative_op) {
+                ENTRYAGGREGATOR => 'OR'
+            );
             $self->Limit(
                 ALIAS           => $ocfvalias,
-                FIELD           => $column || 'Content',
+                FIELD           => 'Content',
                 OPERATOR        => 'IS',
                 VALUE           => 'NULL',
-                ENTRYAGGREGATOR => 'OR',
                 SUBCLAUSE       => $args{SUBCLAUSE},
+                ENTRYAGGREGATOR => 'OR'
             );
+            $self->_CloseParen( $args{SUBCLAUSE} );
+            $self->Limit( $fix_op->(
+                ALIAS           => $ocfvalias,
+                FIELD           => 'LargeContent',
+                OPERATOR        => $op,
+                VALUE           => $value,
+                ENTRYAGGREGATOR => 'AND',
+                SUBCLAUSE       => $args{SUBCLAUSE},
+                CASESENSITIVE => 0,
+            ) );
+            $self->_CloseParen( $args{SUBCLAUSE} );
         }
         $self->_CloseParen( $args{SUBCLAUSE} );
 
+        # XXX: if we join via CustomFields table then
+        # because of order of left joins we get NULLs in
+        # CF table and then get nulls for those records
+        # in OCFVs table what result in wrong results
+        # as decifer method now tries to load a CF then
+        # we fall into this situation only when there
+        # are more than one CF with the name in the DB.
+        # the same thing applies to order by call.
+        # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
+        # we want treat IS NULL as (not applies or has
+        # no value)
+        $self->Limit(
+            ALIAS           => $CFs,
+            FIELD           => 'Name',
+            OPERATOR        => 'IS NOT',
+            VALUE           => 'NULL',
+            ENTRYAGGREGATOR => 'AND',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+        ) if $CFs;
+        $self->_CloseParen( $args{SUBCLAUSE} );
+    }
+
+    if ($negative_op) {
+        $self->Limit(
+            ALIAS           => $ocfvalias,
+            FIELD           => $column || 'Content',
+            OPERATOR        => 'IS',
+            VALUE           => 'NULL',
+            ENTRYAGGREGATOR => 'OR',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+        );
     }
+    $self->_CloseParen( $args{SUBCLAUSE} );
 }
 
 =head2 Limit PARAMHASH

commit 21a3af995fa2d4d5d3b04c94a4f36b26a07d6d5a
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:19:08 2013 -0400

    Push negative-op into we-have-a-column if/else to increase reading locality

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 7f3de96..9d020d7 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -586,10 +586,9 @@ sub _LimitCustomField {
         return;
     }
 
-    $self->_OpenParen( $args{SUBCLAUSE} );
-    # if column is defined then deal only with it
-    # otherwise search in Content and in LargeContent
+    # If column is defined, then we just search it that, with no magic
     if ( $column ) {
+        $self->_OpenParen( $args{SUBCLAUSE} );
         $self->Limit( $fix_op->(
             %args,
             ALIAS      => $ocfvalias,
@@ -598,10 +597,20 @@ sub _LimitCustomField {
             VALUE      => $value,
             CASESENSITIVE => 0,
         ) );
+        $self->Limit(
+            ALIAS           => $ocfvalias,
+            FIELD           => $column,
+            OPERATOR        => 'IS',
+            VALUE           => 'NULL',
+            ENTRYAGGREGATOR => 'OR',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+        ) if $negative_op;
+        $self->_CloseParen( $args{SUBCLAUSE} );
     }
     else {
         $self->_OpenParen( $args{SUBCLAUSE} );
         $self->_OpenParen( $args{SUBCLAUSE} );
+        $self->_OpenParen( $args{SUBCLAUSE} );
         if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
             if ( length( Encode::encode_utf8($value) ) < 256 ) {
                 $self->Limit(
@@ -705,19 +714,19 @@ sub _LimitCustomField {
             SUBCLAUSE       => $args{SUBCLAUSE},
         ) if $CFs;
         $self->_CloseParen( $args{SUBCLAUSE} );
+        if ($negative_op) {
+            $self->Limit(
+                ALIAS           => $ocfvalias,
+                FIELD           => $column || 'Content',
+                OPERATOR        => 'IS',
+                VALUE           => 'NULL',
+                ENTRYAGGREGATOR => 'OR',
+                SUBCLAUSE       => $args{SUBCLAUSE},
+            );
+        }
+        $self->_CloseParen( $args{SUBCLAUSE} );
     }
 
-    if ($negative_op) {
-        $self->Limit(
-            ALIAS           => $ocfvalias,
-            FIELD           => $column || 'Content',
-            OPERATOR        => 'IS',
-            VALUE           => 'NULL',
-            ENTRYAGGREGATOR => 'OR',
-            SUBCLAUSE       => $args{SUBCLAUSE},
-        );
-    }
-    $self->_CloseParen( $args{SUBCLAUSE} );
 }
 
 =head2 Limit PARAMHASH

commit bee02d61647112a8707b3497c9b797b5057598a8
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:20:48 2013 -0400

    Short-circuit from column-specific limit

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 9d020d7..f582870 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -606,53 +606,13 @@ sub _LimitCustomField {
             SUBCLAUSE       => $args{SUBCLAUSE},
         ) if $negative_op;
         $self->_CloseParen( $args{SUBCLAUSE} );
+        return;
     }
-    else {
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
-            if ( length( Encode::encode_utf8($value) ) < 256 ) {
-                $self->Limit(
-                    %args,
-                    ALIAS    => $ocfvalias,
-                    FIELD    => 'Content',
-                    OPERATOR => $op,
-                    VALUE    => $value,
-                    CASESENSITIVE => 0,
-                );
-            }
-            else {
-                $self->_OpenParen( $args{SUBCLAUSE} );
-                $self->Limit(
-                    ALIAS           => $ocfvalias,
-                    FIELD           => 'Content',
-                    OPERATOR        => '=',
-                    VALUE           => '',
-                    ENTRYAGGREGATOR => 'OR',
-                    SUBCLAUSE       => $args{SUBCLAUSE},
-                );
-                $self->Limit(
-                    ALIAS           => $ocfvalias,
-                    FIELD           => 'Content',
-                    OPERATOR        => 'IS',
-                    VALUE           => 'NULL',
-                    ENTRYAGGREGATOR => 'OR',
-                    SUBCLAUSE       => $args{SUBCLAUSE},
-                );
-                $self->_CloseParen( $args{SUBCLAUSE} );
-                $self->Limit( $fix_op->(
-                    ALIAS           => $ocfvalias,
-                    FIELD           => 'LargeContent',
-                    OPERATOR        => $op,
-                    VALUE           => $value,
-                    ENTRYAGGREGATOR => 'AND',
-                    SUBCLAUSE       => $args{SUBCLAUSE},
-                    CASESENSITIVE => 0,
-                ) );
-            }
-        }
-        else {
+    $self->_OpenParen( $args{SUBCLAUSE} );
+    $self->_OpenParen( $args{SUBCLAUSE} );
+    $self->_OpenParen( $args{SUBCLAUSE} );
+    if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
+        if ( length( Encode::encode_utf8($value) ) < 256 ) {
             $self->Limit(
                 %args,
                 ALIAS    => $ocfvalias,
@@ -661,24 +621,24 @@ sub _LimitCustomField {
                 VALUE    => $value,
                 CASESENSITIVE => 0,
             );
-
-            $self->_OpenParen( $args{SUBCLAUSE} );
+        }
+        else {
             $self->_OpenParen( $args{SUBCLAUSE} );
             $self->Limit(
                 ALIAS           => $ocfvalias,
                 FIELD           => 'Content',
                 OPERATOR        => '=',
                 VALUE           => '',
+                ENTRYAGGREGATOR => 'OR',
                 SUBCLAUSE       => $args{SUBCLAUSE},
-                ENTRYAGGREGATOR => 'OR'
             );
             $self->Limit(
                 ALIAS           => $ocfvalias,
                 FIELD           => 'Content',
                 OPERATOR        => 'IS',
                 VALUE           => 'NULL',
+                ENTRYAGGREGATOR => 'OR',
                 SUBCLAUSE       => $args{SUBCLAUSE},
-                ENTRYAGGREGATOR => 'OR'
             );
             $self->_CloseParen( $args{SUBCLAUSE} );
             $self->Limit( $fix_op->(
@@ -690,43 +650,81 @@ sub _LimitCustomField {
                 SUBCLAUSE       => $args{SUBCLAUSE},
                 CASESENSITIVE => 0,
             ) );
-            $self->_CloseParen( $args{SUBCLAUSE} );
         }
-        $self->_CloseParen( $args{SUBCLAUSE} );
+    }
+    else {
+        $self->Limit(
+            %args,
+            ALIAS    => $ocfvalias,
+            FIELD    => 'Content',
+            OPERATOR => $op,
+            VALUE    => $value,
+            CASESENSITIVE => 0,
+        );
 
-        # XXX: if we join via CustomFields table then
-        # because of order of left joins we get NULLs in
-        # CF table and then get nulls for those records
-        # in OCFVs table what result in wrong results
-        # as decifer method now tries to load a CF then
-        # we fall into this situation only when there
-        # are more than one CF with the name in the DB.
-        # the same thing applies to order by call.
-        # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
-        # we want treat IS NULL as (not applies or has
-        # no value)
+        $self->_OpenParen( $args{SUBCLAUSE} );
+        $self->_OpenParen( $args{SUBCLAUSE} );
         $self->Limit(
-            ALIAS           => $CFs,
-            FIELD           => 'Name',
-            OPERATOR        => 'IS NOT',
+            ALIAS           => $ocfvalias,
+            FIELD           => 'Content',
+            OPERATOR        => '=',
+            VALUE           => '',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+            ENTRYAGGREGATOR => 'OR'
+        );
+        $self->Limit(
+            ALIAS           => $ocfvalias,
+            FIELD           => 'Content',
+            OPERATOR        => 'IS',
             VALUE           => 'NULL',
-            ENTRYAGGREGATOR => 'AND',
             SUBCLAUSE       => $args{SUBCLAUSE},
-        ) if $CFs;
+            ENTRYAGGREGATOR => 'OR'
+        );
         $self->_CloseParen( $args{SUBCLAUSE} );
-        if ($negative_op) {
-            $self->Limit(
-                ALIAS           => $ocfvalias,
-                FIELD           => $column || 'Content',
-                OPERATOR        => 'IS',
-                VALUE           => 'NULL',
-                ENTRYAGGREGATOR => 'OR',
-                SUBCLAUSE       => $args{SUBCLAUSE},
-            );
-        }
+        $self->Limit( $fix_op->(
+            ALIAS           => $ocfvalias,
+            FIELD           => 'LargeContent',
+            OPERATOR        => $op,
+            VALUE           => $value,
+            ENTRYAGGREGATOR => 'AND',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+            CASESENSITIVE => 0,
+        ) );
         $self->_CloseParen( $args{SUBCLAUSE} );
     }
-
+    $self->_CloseParen( $args{SUBCLAUSE} );
+
+    # XXX: if we join via CustomFields table then
+    # because of order of left joins we get NULLs in
+    # CF table and then get nulls for those records
+    # in OCFVs table what result in wrong results
+    # as decifer method now tries to load a CF then
+    # we fall into this situation only when there
+    # are more than one CF with the name in the DB.
+    # the same thing applies to order by call.
+    # TODO: reorder joins T <- OCFVs <- CFs <- OCFs if
+    # we want treat IS NULL as (not applies or has
+    # no value)
+    $self->Limit(
+        ALIAS           => $CFs,
+        FIELD           => 'Name',
+        OPERATOR        => 'IS NOT',
+        VALUE           => 'NULL',
+        ENTRYAGGREGATOR => 'AND',
+        SUBCLAUSE       => $args{SUBCLAUSE},
+    ) if $CFs;
+    $self->_CloseParen( $args{SUBCLAUSE} );
+    if ($negative_op) {
+        $self->Limit(
+            ALIAS           => $ocfvalias,
+            FIELD           => $column || 'Content',
+            OPERATOR        => 'IS',
+            VALUE           => 'NULL',
+            ENTRYAGGREGATOR => 'OR',
+            SUBCLAUSE       => $args{SUBCLAUSE},
+        );
+    }
+    $self->_CloseParen( $args{SUBCLAUSE} );
 }
 
 =head2 Limit PARAMHASH

commit 0011c4ec6066bf2fc1709cb17d1c43211b8db924
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Apr 30 15:29:09 2013 -0400

    Merge two parallel content search branches
    
    The only occasion upon which Content should not be searched is if the
    search is an equality search and the value we are matching is certainly
    larger than would fit into Content.  Similarly, skip looking at
    LargeContent iff the value is short and it is an equality.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index f582870..c00b037 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -608,51 +608,13 @@ sub _LimitCustomField {
         $self->_CloseParen( $args{SUBCLAUSE} );
         return;
     }
-    $self->_OpenParen( $args{SUBCLAUSE} );
-    $self->_OpenParen( $args{SUBCLAUSE} );
-    $self->_OpenParen( $args{SUBCLAUSE} );
-    if ( $op eq '=' || $op eq '!=' || $op eq '<>' ) {
-        if ( length( Encode::encode_utf8($value) ) < 256 ) {
-            $self->Limit(
-                %args,
-                ALIAS    => $ocfvalias,
-                FIELD    => 'Content',
-                OPERATOR => $op,
-                VALUE    => $value,
-                CASESENSITIVE => 0,
-            );
-        }
-        else {
-            $self->_OpenParen( $args{SUBCLAUSE} );
-            $self->Limit(
-                ALIAS           => $ocfvalias,
-                FIELD           => 'Content',
-                OPERATOR        => '=',
-                VALUE           => '',
-                ENTRYAGGREGATOR => 'OR',
-                SUBCLAUSE       => $args{SUBCLAUSE},
-            );
-            $self->Limit(
-                ALIAS           => $ocfvalias,
-                FIELD           => 'Content',
-                OPERATOR        => 'IS',
-                VALUE           => 'NULL',
-                ENTRYAGGREGATOR => 'OR',
-                SUBCLAUSE       => $args{SUBCLAUSE},
-            );
-            $self->_CloseParen( $args{SUBCLAUSE} );
-            $self->Limit( $fix_op->(
-                ALIAS           => $ocfvalias,
-                FIELD           => 'LargeContent',
-                OPERATOR        => $op,
-                VALUE           => $value,
-                ENTRYAGGREGATOR => 'AND',
-                SUBCLAUSE       => $args{SUBCLAUSE},
-                CASESENSITIVE => 0,
-            ) );
-        }
-    }
-    else {
+
+    $self->_OpenParen( $args{SUBCLAUSE} ); # For negative_op "OR it is null" clause
+    $self->_OpenParen( $args{SUBCLAUSE} ); # NAME IS NOT NULL clause
+
+    my $value_is_long = (length( Encode::encode_utf8($value)) > 255) ? 1 : 0;
+    $self->_OpenParen( $args{SUBCLAUSE} ); # Check Content / LargeContent
+    unless ($value_is_long and $op =~ /^(=|!=|<>)$/) {
         $self->Limit(
             %args,
             ALIAS    => $ocfvalias,
@@ -661,26 +623,27 @@ sub _LimitCustomField {
             VALUE    => $value,
             CASESENSITIVE => 0,
         );
-
-        $self->_OpenParen( $args{SUBCLAUSE} );
-        $self->_OpenParen( $args{SUBCLAUSE} );
+    }
+    unless (!$value_is_long and $op =~ /^(=|!=|<>)$/) {
+        $self->_OpenParen( $args{SUBCLAUSE} ); # LargeContent check
+        $self->_OpenParen( $args{SUBCLAUSE} ); # Content is null?
         $self->Limit(
             ALIAS           => $ocfvalias,
             FIELD           => 'Content',
             OPERATOR        => '=',
             VALUE           => '',
+            ENTRYAGGREGATOR => 'OR',
             SUBCLAUSE       => $args{SUBCLAUSE},
-            ENTRYAGGREGATOR => 'OR'
         );
         $self->Limit(
             ALIAS           => $ocfvalias,
             FIELD           => 'Content',
             OPERATOR        => 'IS',
             VALUE           => 'NULL',
+            ENTRYAGGREGATOR => 'OR',
             SUBCLAUSE       => $args{SUBCLAUSE},
-            ENTRYAGGREGATOR => 'OR'
         );
-        $self->_CloseParen( $args{SUBCLAUSE} );
+        $self->_CloseParen( $args{SUBCLAUSE} ); # Content is null?
         $self->Limit( $fix_op->(
             ALIAS           => $ocfvalias,
             FIELD           => 'LargeContent',
@@ -690,9 +653,10 @@ sub _LimitCustomField {
             SUBCLAUSE       => $args{SUBCLAUSE},
             CASESENSITIVE => 0,
         ) );
-        $self->_CloseParen( $args{SUBCLAUSE} );
+        $self->_CloseParen( $args{SUBCLAUSE} ); # LargeContent check
     }
-    $self->_CloseParen( $args{SUBCLAUSE} );
+
+    $self->_CloseParen( $args{SUBCLAUSE} ); # Check Content/LargeContent
 
     # XXX: if we join via CustomFields table then
     # because of order of left joins we get NULLs in
@@ -713,18 +677,19 @@ sub _LimitCustomField {
         ENTRYAGGREGATOR => 'AND',
         SUBCLAUSE       => $args{SUBCLAUSE},
     ) if $CFs;
-    $self->_CloseParen( $args{SUBCLAUSE} );
-    if ($negative_op) {
-        $self->Limit(
-            ALIAS           => $ocfvalias,
-            FIELD           => $column || 'Content',
-            OPERATOR        => 'IS',
-            VALUE           => 'NULL',
-            ENTRYAGGREGATOR => 'OR',
-            SUBCLAUSE       => $args{SUBCLAUSE},
-        );
-    }
-    $self->_CloseParen( $args{SUBCLAUSE} );
+    $self->_CloseParen( $args{SUBCLAUSE} ); # Name IS NOT NULL clause
+
+    # If we were looking for != or NOT LIKE, we need to include the
+    # possibility that the row had no value.
+    $self->Limit(
+        ALIAS           => $ocfvalias,
+        FIELD           => $column || 'Content',
+        OPERATOR        => 'IS',
+        VALUE           => 'NULL',
+        ENTRYAGGREGATOR => 'OR',
+        SUBCLAUSE       => $args{SUBCLAUSE},
+    ) if $negative_op;
+    $self->_CloseParen( $args{SUBCLAUSE} ); # negative_op clause
 }
 
 =head2 Limit PARAMHASH

commit b1d6b927858961dad4bd8d2703c7fc8dd80bcbf4
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed May 1 22:59:59 2013 -0400

    Add and test LongContent values; some tests fail

diff --git a/t/ticket/search_by_cf_freeform_multiple.t b/t/ticket/search_by_cf_freeform_multiple.t
index 4772beb..a4a1a40 100644
--- a/t/ticket/search_by_cf_freeform_multiple.t
+++ b/t/ticket/search_by_cf_freeform_multiple.t
@@ -21,6 +21,7 @@ my ($cf_name, $cf_id, $cf) = ("Test", 0, undef);
     $cf_id = $cf->id;
 }
 
+my $ylong = "y" x 300;
 subtest "Creating tickets" => sub {
     RT::Test->create_tickets( { Queue => $q->id },
         { Subject => '-' },
@@ -30,80 +31,88 @@ subtest "Creating tickets" => sub {
         { Subject => 'xy', "CustomField-$cf_id" => [ 'x', 'y' ], },
         { Subject => 'xz', "CustomField-$cf_id" => [ 'x', 'z' ], },
         { Subject => 'yz', "CustomField-$cf_id" => [ 'y', 'z' ], },
+        { Subject => 'x_ylong', "CustomField-$cf_id" => [ 'x', $ylong ], },
+        { Subject => 'ylong', "CustomField-$cf_id" => $ylong, },
     );
 };
 
 my @tests = (
-    "CF.{$cf_id} IS NULL"                      => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "CF.{$cf_id}.Content IS NULL"              => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "CF.{$cf_id}.LargeContent IS NULL"         => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}' IS NULL"                  => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}.Content' IS NULL"          => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}.LargeContent' IS NULL"     => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_id}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_name}' IS NULL"           => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-
-    "CF.{$cf_id} IS NOT NULL"                  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "CF.{$cf_id}.Content IS NOT NULL"          => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "CF.{$cf_id}.LargeContent IS NOT NULL"     => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' IS NOT NULL"              => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}.Content' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}.LargeContent' IS NOT NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' IS NOT NULL"         => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_name}' IS NOT NULL"       => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-
-    "CF.{$cf_id} = 'x'"                   => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "CF.{$cf_id}.LargeContent = 'x'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-
-    "CF.{$cf_id} != 'x'"                  => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-    "CF.{$cf_id}.Content != 'x'"          => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-    "CF.{$cf_id}.LargeContent != 'x'"     => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}' != 'x'"              => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-    "'CF.{$cf_name}.Content' != 'x'"      => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-    "'CF.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_id}' != 'x'"         => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-    "'CF.$queue.{$cf_name}' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0 },
-
-    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1 },
+    "CF.{$cf_id} IS NULL"                      => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id}.Content IS NULL"              => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent IS NULL"         => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}' IS NULL"                  => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.{$cf_name}.Content' IS NULL"          => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "'CF.{$cf_name}.LargeContent' IS NULL"     => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_id}' IS NULL"             => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' IS NULL"           => { '-' => 1, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+
+    "CF.{$cf_id} IS NOT NULL"                  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.Content IS NOT NULL"          => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "CF.{$cf_id}.LargeContent IS NOT NULL"     => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "'CF.{$cf_name}' IS NOT NULL"              => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.{$cf_name}.Content' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}.LargeContent' IS NOT NULL" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' IS NOT NULL"         => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' IS NOT NULL"       => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+
+    "CF.{$cf_id} = 'x'"                   => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "CF.{$cf_id}.Content = 'x'"           => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "CF.{$cf_id}.LargeContent = 'x'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id} = '$ylong'"              => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.Content = '$ylong'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id}.LargeContent = '$ylong'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x'"          => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x'"        => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+
+    "CF.{$cf_id} != 'x'"                   => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+    "CF.{$cf_id}.Content != 'x'"           => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+    "CF.{$cf_id}.LargeContent != 'x'"      => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id} != '$ylong'"              => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id}.Content != '$ylong'"      => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent != '$ylong'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
+    "'CF.{$cf_name}' != 'x'"               => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+    "'CF.{$cf_name}.LargeContent' != 'x'"  => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' != 'x'"          => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+    "'CF.$queue.{$cf_name}' != 'x'"        => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 1, y => 1, z => 0, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 1, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+
+    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 },
+    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 },
+    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 },
+    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, x => 0, y => 0, z => 1, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 1 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
 );
 run_tests(@tests);
 
diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index 74fd057..a5a5a94 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -24,6 +24,7 @@ my ($cf_name, $cf_id, $cf) = ("Test", 0, undef);
 my $other_q = RT::Test->load_or_create_queue( Name => 'Other' );
 ok $other_q && $other_q->id, 'loaded or created queue';
 
+my $ylong = 'y' x 300;
 subtest "Creating tickets" => sub {
     RT::Test->create_tickets( { Queue => $q->id },
         { Subject => '-' },
@@ -31,88 +32,95 @@ subtest "Creating tickets" => sub {
         { Subject => 'x', "CustomField-$cf_id" => 'x', },
         { Subject => 'y', "CustomField-$cf_id" => 'y', },
         { Subject => 'z', "CustomField-$cf_id" => 'z', },
+        { Subject => 'ylong', "CustomField-$cf_id" => $ylong, },
     );
 };
 
 my @tests = (
-    "CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "CF.{$cf_id}.Content IS NULL"                => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "CF.{$cf_id}.LargeContent IS NULL"           => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' IS NULL"                    => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}.Content' IS NULL"            => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}.LargeContent' IS NULL"       => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' IS NULL"               => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' IS NULL"             => { '-' => 1, other => 1, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} IS NOT NULL"                    => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "CF.{$cf_id}.Content IS NOT NULL"            => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "CF.{$cf_id}.LargeContent IS NOT NULL"       => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}.Content' IS NOT NULL"        => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}.LargeContent' IS NOT NULL"   => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' IS NOT NULL"           => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' IS NOT NULL"         => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-
-    "CF.{$cf_id} = 'x'"                          => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "CF.{$cf_id}.Content = 'x'"                  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "CF.{$cf_id}.LargeContent = 'x'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x'"                      => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}.Content' = 'x'"              => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}.LargeContent' = 'x'"         => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x'"                 => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}.Content' = 'x'"         => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}.LargeContent' = 'x'"    => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x'"               => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}.Content' = 'x'"       => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} != 'x'"                         => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "CF.{$cf_id}.Content != 'x'"                 => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "CF.{$cf_id}.LargeContent != 'x'"            => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' != 'x'"                     => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}.Content' != 'x'"             => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.{$cf_name}.LargeContent' != 'x'"        => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' != 'x'"                => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}.Content' != 'x'"        => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}.LargeContent' != 'x'"   => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' != 'x'"              => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}.Content' != 'x'"      => { '-' => 1, other => 1, x => 0, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 1, y => 1, z => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
-    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
-    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
-    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, other => 1, x => 0, y => 0, z => 1 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, other => 1, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 0, z => 0 },
-
-    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
-    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 1, z => 1 },
+    "CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id}.Content IS NULL"                => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 1 },
+    "CF.{$cf_id}.LargeContent IS NULL"           => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "'CF.{$cf_name}' IS NULL"                    => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}.Content' IS NULL"            => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 1 },
+    "'CF.{$cf_name}.LargeContent' IS NULL"       => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "'CF.$queue.{$cf_id}' IS NULL"               => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' IS NULL"             => { '-' => 1, other => 1, x => 0, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} IS NOT NULL"                    => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id}.Content IS NOT NULL"            => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 0 },
+    "CF.{$cf_id}.LargeContent IS NOT NULL"       => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.{$cf_name}.Content' IS NOT NULL"        => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 0 },
+    "'CF.{$cf_name}.LargeContent' IS NOT NULL"   => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "'CF.$queue.{$cf_id}' IS NOT NULL"           => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' IS NOT NULL"         => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+
+    "CF.{$cf_id} = 'x'"                          => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id}.Content = 'x'"                  => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id}.LargeContent = 'x'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id} = '$ylong'"                     => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "CF.{$cf_id}.Content = '$ylong'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id}.LargeContent = '$ylong'"        => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "'CF.{$cf_name}' = 'x'"                      => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}.Content' = 'x'"              => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}.LargeContent' = 'x'"         => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x'"                 => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}.Content' = 'x'"         => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}.LargeContent' = 'x'"    => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x'"               => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}.Content' = 'x'"       => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} != 'x'"                         => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id}.Content != 'x'"                 => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent != 'x'"            => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id} != '$ylong'"                    => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "CF.{$cf_id}.Content != '$ylong'"            => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent != '$ylong'"       => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "'CF.{$cf_name}' != 'x'"                     => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.{$cf_name}.Content' != 'x'"             => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.{$cf_name}.LargeContent' != 'x'"        => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' != 'x'"                => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}.Content' != 'x'"        => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}.LargeContent' != 'x'"   => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' != 'x'"              => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}.Content' != 'x'"      => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}.LargeContent' != 'x'" => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 1, y => 1, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} = 'y'"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' = 'y'"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' = 'y'"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' = 'y'"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} != 'x' AND CF.{$cf_id} != 'y'"                        => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 },
+    "'CF.{$cf_name}' != 'x' AND 'CF.{$cf_name}' != 'y'"                => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' != 'x' AND 'CF.$queue.{$cf_id}' != 'y'"      => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' != 'x' AND 'CF.$queue.{$cf_name}' != 'y'"  => { '-' => 1, other => 1, x => 0, y => 0, z => 1, ylong => 1 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NULL"                        => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NULL"                => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NULL"                        => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NULL"                => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NULL"      => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NULL"  => { '-' => 1, other => 1, x => 1, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' AND CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.{$cf_name}' = 'x' AND 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_id}' = 'x' AND 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+    "'CF.$queue.{$cf_name}' = 'x' AND 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
+
+    "CF.{$cf_id} = 'x' OR CF.{$cf_id} IS NOT NULL"                        => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.{$cf_name}' = 'x' OR 'CF.{$cf_name}' IS NOT NULL"                => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_id}' = 'x' OR 'CF.$queue.{$cf_id}' IS NOT NULL"      => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
+    "'CF.$queue.{$cf_name}' = 'x' OR 'CF.$queue.{$cf_name}' IS NOT NULL"  => { '-' => 0, other => 0, x => 1, y => 1, z => 1, ylong => 1 },
 );
 run_tests(@tests);
 

commit ac13502595d2009888abc577f3ccbfce157a61b9
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu May 2 00:00:19 2013 -0400

    When a value overflows into LargeContent, ensure that Content is null
    
    The ->AddCustomFieldValue codepath attempts to ensure that empty values
    for Content or LargeContent are never passed down the stack for
    insertion.  However, this is foiled if a >255-character value is passed
    through for Value, is only caught at the ObjectCustomFieldValue->Create
    level -- which sets Content to the empty string when upgrading the
    Content to LargeContent, rather than undef.
    
    Ensure that NULLs are properly stored in the Content column when
    LargeContent is used, by setting Content to undef in such cases.  There
    may also exist historical OCFVs which contain '' instead of NULL; update
    them accordingly.

diff --git a/etc/upgrade/4.1.10/schema.Oracle b/etc/upgrade/4.1.10/schema.Oracle
new file mode 100644
index 0000000..93f036f
--- /dev/null
+++ b/etc/upgrade/4.1.10/schema.Oracle
@@ -0,0 +1 @@
+-- No update is necessary, given that '' == NULL on Oracle
diff --git a/etc/upgrade/4.1.10/schema.Pg b/etc/upgrade/4.1.10/schema.Pg
new file mode 100644
index 0000000..af862b6
--- /dev/null
+++ b/etc/upgrade/4.1.10/schema.Pg
@@ -0,0 +1 @@
+UPDATE ObjectCustomFieldValues SET Content = NULL WHERE LargeContent IS NOT NULL AND Content = '';
diff --git a/etc/upgrade/4.1.10/schema.mysql b/etc/upgrade/4.1.10/schema.mysql
new file mode 100644
index 0000000..af862b6
--- /dev/null
+++ b/etc/upgrade/4.1.10/schema.mysql
@@ -0,0 +1 @@
+UPDATE ObjectCustomFieldValues SET Content = NULL WHERE LargeContent IS NOT NULL AND Content = '';
diff --git a/lib/RT/ObjectCustomFieldValue.pm b/lib/RT/ObjectCustomFieldValue.pm
index bb360e3..1e8de8b 100644
--- a/lib/RT/ObjectCustomFieldValue.pm
+++ b/lib/RT/ObjectCustomFieldValue.pm
@@ -119,7 +119,7 @@ sub Create {
         }
         else {
             $args{'LargeContent'} = $args{'Content'};
-            $args{'Content'} = '';
+            $args{'Content'} = undef;
             $args{'ContentType'} ||= 'text/plain';
         }
     }

commit eca2558b86a20ca1b09e3218a6d097719c3bed87
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri May 10 02:05:36 2013 -0400

    Improve != searching in combination with LargeContent
    
    When attempting to perform a != on a single-value CF, with a value
    longer than 255 characters, the search is limited by:
    
      ((Content = '' OR Content IS NULL) AND LargeContent != '...')
    
    The limitation «Content != '...' OR » was intentionally omitted, based
    on the proposition that a value longer than 255 characters cannot relate
    to the Content field.  However, _any_ value in the Content field
    necessarily implies a custom field value which does not match the long
    string (because it is too short), meaning that the ticket should be
    contained in the resultset.
    
    Conflicts:
    	lib/RT/SearchBuilder.pm

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index c00b037..974ec12 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -614,7 +614,20 @@ sub _LimitCustomField {
 
     my $value_is_long = (length( Encode::encode_utf8($value)) > 255) ? 1 : 0;
     $self->_OpenParen( $args{SUBCLAUSE} ); # Check Content / LargeContent
-    unless ($value_is_long and $op =~ /^(=|!=|<>)$/) {
+    if ($value_is_long and $op eq "=") {
+        # Doesn't matter what Content contains, as it cannot match the
+        # too-long value; we just look in LargeContent, below.
+    } elsif ($value_is_long and $op =~ /^(!=|<>)$/) {
+        # If Content is non-null, that's a valid way to _not_ contain the too-long value.
+        $self->Limit(
+            %args,
+            ALIAS    => $ocfvalias,
+            FIELD    => 'Content',
+            OPERATOR => 'IS NOT',
+            VALUE    => 'NULL',
+        );
+    } else {
+        # Otherwise, go looking at the Content
         $self->Limit(
             %args,
             ALIAS    => $ocfvalias,
@@ -624,7 +637,22 @@ sub _LimitCustomField {
             CASESENSITIVE => 0,
         );
     }
-    unless (!$value_is_long and $op =~ /^(=|!=|<>)$/) {
+
+    if (!$value_is_long and $op eq "=") {
+        # Doesn't matter what LargeContent contains, as it cannot match
+        # the short value.
+    } elsif (!$value_is_long and $op =~ /^(!=|<>)$/) {
+        # If LargeContent is non-null, that's a valid way to _not_
+        # contain the too-short value.
+        $self->Limit(
+            %args,
+            ALIAS    => $ocfvalias,
+            FIELD    => 'LargeContent',
+            OPERATOR => 'IS NOT',
+            VALUE    => 'NULL',
+            ENTRYAGGREGATOR => 'OR',
+        );
+    } else {
         $self->_OpenParen( $args{SUBCLAUSE} ); # LargeContent check
         $self->_OpenParen( $args{SUBCLAUSE} ); # Content is null?
         $self->Limit(

commit 8ee882067482416cc63a5ac9131a3de757a96563
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu May 2 01:40:02 2013 -0400

    Improve != and NOT LIKE searching in combination with LargeContent
    
    When attempting to perform a NOT LIKE (or != with a long value) on a
    single-value CF, the search is limited by:
    
      Content NOT LIKE '%value%' OR
      ((Content = '' OR Content IS NULL) AND LargeContent NOT LIKE '%value%')
    
    However, it must also be open to the possibility that the ticket did not
    contain a value for that CF, which is a valid method of having
    not-that-value.  It thus also added the possibility for lack-of-value by
    further limiting by:
    
       OR Content IS NULL
    
    Unfortunately, as Content is nearly definitionally NULL for rows with
    LargeContent, this results in a false positives where LargeContent
    matches '%value%' and thus the ticket should not match.
    
    The limitation of "or the ticket could fail to have a value for the CF"
    is better stated by checking that OCFV.id IS NULL, rather than
    OCFV.Content IS NULL; switch to this to avoid the false positive.  Note
    that $column cannot be defined at this point, as it has previously been
    checked for truth, and return'd already in such cases, so the removing
    the reference to $column is intentional.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 974ec12..2f93e91 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -711,7 +711,7 @@ sub _LimitCustomField {
     # possibility that the row had no value.
     $self->Limit(
         ALIAS           => $ocfvalias,
-        FIELD           => $column || 'Content',
+        FIELD           => 'id',
         OPERATOR        => 'IS',
         VALUE           => 'NULL',
         ENTRYAGGREGATOR => 'OR',
diff --git a/t/ticket/search_by_cf_freeform_single.t b/t/ticket/search_by_cf_freeform_single.t
index a5a5a94..0463412 100644
--- a/t/ticket/search_by_cf_freeform_single.t
+++ b/t/ticket/search_by_cf_freeform_single.t
@@ -61,6 +61,9 @@ my @tests = (
     "CF.{$cf_id} = '$ylong'"                     => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
     "CF.{$cf_id}.Content = '$ylong'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
     "CF.{$cf_id}.LargeContent = '$ylong'"        => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "CF.{$cf_id} LIKE 'yyy'"                     => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
+    "CF.{$cf_id}.Content LIKE 'yyy'"             => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
+    "CF.{$cf_id}.LargeContent LIKE 'yyy'"        => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 1 },
     "'CF.{$cf_name}' = 'x'"                      => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
     "'CF.{$cf_name}.Content' = 'x'"              => { '-' => 0, other => 0, x => 1, y => 0, z => 0, ylong => 0 },
     "'CF.{$cf_name}.LargeContent' = 'x'"         => { '-' => 0, other => 0, x => 0, y => 0, z => 0, ylong => 0 },
@@ -77,6 +80,9 @@ my @tests = (
     "CF.{$cf_id} != '$ylong'"                    => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
     "CF.{$cf_id}.Content != '$ylong'"            => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
     "CF.{$cf_id}.LargeContent != '$ylong'"       => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "CF.{$cf_id} NOT LIKE 'yyy'"                 => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
+    "CF.{$cf_id}.Content NOT LIKE 'yyy'"         => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent NOT LIKE 'yyy'"    => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 0 },
     "'CF.{$cf_name}' != 'x'"                     => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
     "'CF.{$cf_name}.Content' != 'x'"             => { '-' => 1, other => 1, x => 0, y => 1, z => 1, ylong => 1 },
     "'CF.{$cf_name}.LargeContent' != 'x'"        => { '-' => 1, other => 1, x => 1, y => 1, z => 1, ylong => 1 },

commit 26bbd612ef3e5250418041db04d59af1c5223ead
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Thu May 2 04:14:01 2013 -0400

    Improve !=, and note the limitations of NOT LIKE, with multi-value LargeContent
    
    When attempting to perform a != or NOT LIKE search on a multi-value CF,
    with a value longer than 255 characters, the search limits the LEFT JOIN
    based on the opposite condition.  However, it assumed the Content column
    unless explicitly instructed; this means that, when checking against a
    long string, it would return false positives.
    
    For the case of !=, we can determine which of Content or LargeContent to
    examine ahead of time.  Unfortunately, for NOT LIKE, we would be forced
    to use a more complex limit to search both:
    
       Content LIKE '%value%' OR
       ((Content = '' OR Content IS NULL) AND LargeContent LIKE '%value%')
    
    ...and such a limit cannot be added to a LEFTJOIN clause using the
    current DBIx::SearchBuilder.

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 2f93e91..294836e 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -557,6 +557,7 @@ sub _LimitCustomField {
 
     my $single_value = !blessed($cf) || $cf->SingleValue;
     my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
+    my $value_is_long = (length( Encode::encode_utf8($value)) > 255) ? 1 : 0;
 
     $cfkey .= '.'. $self->{'_sql_multiple_cfs_index'}++
         if not $single_value and $op =~ /^(!?=|(NOT )?LIKE)$/i;
@@ -568,10 +569,17 @@ sub _LimitCustomField {
         # Reverse the limit we apply to the join, and check IS NULL
         $op =~ s/!|NOT\s+//i;
 
+        # Ideally we would check both Content and LargeContent here, as
+        # the positive searches do below -- however, we cannot place
+        # complex limits inside LEFTJOINs due to searchbuilder
+        # limitations.  Guessing which to check based on the value's
+        # string length is sufficient for !=, but sadly insufficient for
+        # NOT LIKE checks, giving false positives.
+        $column ||= $value_is_long ? 'LargeContent' : 'Content';
         $self->Limit( $fix_op->(
             LEFTJOIN   => $ocfvalias,
             ALIAS      => $ocfvalias,
-            FIELD      => ($column || 'Content'),
+            FIELD      => $column,
             OPERATOR   => $op,
             VALUE      => $value,
             CASESENSITIVE => 0,
@@ -612,7 +620,6 @@ sub _LimitCustomField {
     $self->_OpenParen( $args{SUBCLAUSE} ); # For negative_op "OR it is null" clause
     $self->_OpenParen( $args{SUBCLAUSE} ); # NAME IS NOT NULL clause
 
-    my $value_is_long = (length( Encode::encode_utf8($value)) > 255) ? 1 : 0;
     $self->_OpenParen( $args{SUBCLAUSE} ); # Check Content / LargeContent
     if ($value_is_long and $op eq "=") {
         # Doesn't matter what Content contains, as it cannot match the
diff --git a/t/ticket/search_by_cf_freeform_multiple.t b/t/ticket/search_by_cf_freeform_multiple.t
index a4a1a40..1324abd 100644
--- a/t/ticket/search_by_cf_freeform_multiple.t
+++ b/t/ticket/search_by_cf_freeform_multiple.t
@@ -61,6 +61,9 @@ my @tests = (
     "CF.{$cf_id} = '$ylong'"              => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
     "CF.{$cf_id}.Content = '$ylong'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
     "CF.{$cf_id}.LargeContent = '$ylong'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id} LIKE 'yyy'"              => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.Content LIKE 'yyy'"      => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id}.LargeContent LIKE 'yyy'" => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 1, ylong => 1 },
     "'CF.{$cf_name}' = 'x'"               => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
     "'CF.{$cf_name}.Content' = 'x'"       => { '-' => 0, x => 1, y => 0, z => 0, xy => 1, xz => 1, yz => 0, x_ylong => 1, ylong => 0 },
     "'CF.{$cf_name}.LargeContent' = 'x'"  => { '-' => 0, x => 0, y => 0, z => 0, xy => 0, xz => 0, yz => 0, x_ylong => 0, ylong => 0 },
@@ -73,6 +76,9 @@ my @tests = (
     "CF.{$cf_id} != '$ylong'"              => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
     "CF.{$cf_id}.Content != '$ylong'"      => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
     "CF.{$cf_id}.LargeContent != '$ylong'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
+"TODO: CF.{$cf_id} NOT LIKE 'yyy'"         => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
+    "CF.{$cf_id}.Content NOT LIKE 'yyy'"   => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
+    "CF.{$cf_id}.LargeContent NOT LIKE 'yyy'" => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 0, ylong => 0 },
     "'CF.{$cf_name}' != 'x'"               => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
     "'CF.{$cf_name}.Content' != 'x'"       => { '-' => 1, x => 0, y => 1, z => 1, xy => 0, xz => 0, yz => 1, x_ylong => 0, ylong => 1 },
     "'CF.{$cf_name}.LargeContent' != 'x'"  => { '-' => 1, x => 1, y => 1, z => 1, xy => 1, xz => 1, yz => 1, x_ylong => 1, ylong => 1 },
@@ -122,6 +128,7 @@ sub run_tests {
     while (@tests) {
         my $query = shift @tests;
         my %results = %{ shift @tests };
+        local $TODO = "Not implemented correctly" if $query =~ s/^TODO:\s*//;
         subtest $query => sub {
             my $tix = RT::Tickets->new(RT->SystemUser);
             $tix->FromSQL( "$query" );

commit fe2640c7b5e72e98be515b7af9a16e4782022574
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri May 10 00:37:13 2013 -0400

    Save possibly multiple calls to $cf->Type

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 294836e..89ad069 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -401,16 +401,15 @@ sub _LimitCustomField {
 
     ########## Content pre-parsing if we know things about the CF
     if ( blessed($cf) ) {
-        if ( $cf->Type eq 'IPAddress' ) {
+        my $type = $cf->Type;
+        if ( $type eq 'IPAddress' ) {
             my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
             if ($parsed) {
                 $value = $parsed;
             } else {
                 $RT::Logger->warn("$value is not a valid IPAddress");
             }
-        }
-
-        if ( $cf->Type eq 'IPAddressRange' ) {
+        } elsif ( $type eq 'IPAddressRange' ) {
             if ( $value =~ /^\s*$RE{net}{CIDR}{IPv4}{-keep}\s*$/o ) {
                 # convert incomplete 192.168/24 to 192.168.0.0/24 format
                 $value =
@@ -475,9 +474,7 @@ sub _LimitCustomField {
                 $self->_CloseParen( $args{SUBCLAUSE} );
                 return;
             }
-        }
-
-        if ( $cf->Type =~ /^Date(?:Time)?$/ ) {
+        } elsif ( $type =~ /^Date(?:Time)?$/ ) {
             my $date = RT::Date->new( $self->CurrentUser );
             $date->Set( Format => 'unknown', Value => $value );
             if ( $date->Unix ) {

commit cb1a739cfb79b125a441a6dbe2a862049218b6d3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri May 10 01:11:18 2013 -0400

    Ensure that values are not pre-parsed more than once

diff --git a/lib/RT/SearchBuilder.pm b/lib/RT/SearchBuilder.pm
index 89ad069..0ef3d5d 100644
--- a/lib/RT/SearchBuilder.pm
+++ b/lib/RT/SearchBuilder.pm
@@ -355,6 +355,7 @@ sub _LimitCustomField {
                  CUSTOMFIELD  => undef,
                  OPERATOR     => '=',
                  KEY          => undef,
+                 PREPARSE     => 1,
                  @_ );
 
     my $op     = delete $args{OPERATOR};
@@ -400,7 +401,7 @@ sub _LimitCustomField {
     };
 
     ########## Content pre-parsing if we know things about the CF
-    if ( blessed($cf) ) {
+    if ( blessed($cf) and delete $args{PREPARSE} ) {
         my $type = $cf->Type;
         if ( $type eq 'IPAddress' ) {
             my $parsed = RT::ObjectCustomFieldValue->ParseIP($value);
@@ -445,6 +446,7 @@ sub _LimitCustomField {
                         VALUE       => $end_ip,
                         CUSTOMFIELD => $cf,
                         COLUMN      => 'Content',
+                        PREPARSE    => 0,
                     );
                     $self->_LimitCustomField(
                         %args,
@@ -453,6 +455,7 @@ sub _LimitCustomField {
                         CUSTOMFIELD => $cf,
                         COLUMN      => 'LargeContent',
                         ENTRYAGGREGATOR => 'AND',
+                        PREPARSE    => 0,
                     );
                 } else { # negative equation
                     $self->_LimitCustomField(
@@ -461,6 +464,7 @@ sub _LimitCustomField {
                         VALUE       => $end_ip,
                         CUSTOMFIELD => $cf,
                         COLUMN      => 'Content',
+                        PREPARSE    => 0,
                     );
                     $self->_LimitCustomField(
                         %args,
@@ -469,6 +473,7 @@ sub _LimitCustomField {
                         CUSTOMFIELD => $cf,
                         COLUMN      => 'LargeContent',
                         ENTRYAGGREGATOR => 'OR',
+                        PREPARSE    => 0,
                     );
                 }
                 $self->_CloseParen( $args{SUBCLAUSE} );
@@ -511,6 +516,7 @@ sub _LimitCustomField {
                     CUSTOMFIELD     => $cf,
                     COLUMN          => 'Content',
                     ENTRYAGGREGATOR => 'AND',
+                    PREPARSE        => 0,
                 );
 
                 $self->_LimitCustomField(
@@ -520,6 +526,7 @@ sub _LimitCustomField {
                     CUSTOMFIELD     => $cf,
                     COLUMN          => 'Content',
                     ENTRYAGGREGATOR => 'AND',
+                    PREPARSE        => 0,
                 );
                 $self->_CloseParen( $args{SUBCLAUSE} );
                 return;

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


More information about the Rt-commit mailing list