X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FTickets_SQL.pm;h=f667b0699943177c3f94cf3d2b671a329900f232;hb=b8988e1d3ac75af63c85e8563e57701030315a9e;hp=ec1bb4997ba1774c19bd408b6cb7a27063b5617a;hpb=85e677b86fc37c54e6de2b06340351a28f5a5916;p=freeside.git diff --git a/rt/lib/RT/Tickets_SQL.pm b/rt/lib/RT/Tickets_SQL.pm index ec1bb4997..f667b0699 100644 --- a/rt/lib/RT/Tickets_SQL.pm +++ b/rt/lib/RT/Tickets_SQL.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -57,10 +57,7 @@ use RT::SQL; # Import configuration data from the lexcial scope of __PACKAGE__ (or # at least where those two Subroutines are defined.) -our (%FIELD_METADATA, %dispatch, %can_bundle); - -# Lower Case version of FIELDS, for case insensitivity -my %lcfields = map { ( lc($_) => $_ ) } (keys %FIELD_METADATA); +our (%FIELD_METADATA, %LOWER_CASE_FIELDS, %dispatch, %can_bundle); sub _InitSQL { my $self = shift; @@ -77,7 +74,7 @@ sub _InitSQL { sub _SQLLimit { my $self = shift; - my %args = (@_); + my %args = (FIELD => '', @_); if ($args{'FIELD'} eq 'EffectiveId' && (!$args{'ALIAS'} || $args{'ALIAS'} eq 'main' ) ) { $self->{'looking_at_effective_id'} = 1; @@ -174,27 +171,77 @@ sub _parser { my @bundle; my $ea = ''; + # Bundling of joins is implemented by dynamically tracking a parallel query + # tree in %sub_tree as the TicketSQL is parsed. Don't be fooled by + # _close_bundle(), @bundle, and %can_bundle; they are completely unused for + # quite a long time and removed in RT 4.2. For now they stay, a useless + # relic. + # + # Only positive, OR'd watcher conditions are bundled currently. Each key + # in %sub_tree is a watcher type (Requestor, Cc, AdminCc) or the generic + # "Watcher" for any watcher type. Owner is not bundled because it is + # denormalized into a Tickets column and doesn't need a join. AND'd + # conditions are not bundled since a record may have multiple watchers + # which independently match the conditions, thus necessitating two joins. + # + # The values of %sub_tree are arrayrefs made up of: + # + # * Open parentheses "(" pushed on by the OpenParen callback + # * Arrayrefs of bundled join aliases pushed on by the Condition callback + # * Entry aggregators (AND/OR) pushed on by the EntryAggregator callback + # + # The CloseParen callback takes care of backing off the query trees until + # outside of the just-closed parenthetical, thus restoring the tree state + # an equivalent of before the parenthetical was entered. + # + # The Condition callback handles starting a new subtree or extending an + # existing one, determining if bundling the current condition with any + # subtree is possible, and pruning any dangling entry aggregators from + # trees. + # + + my %sub_tree; + my $depth = 0; + my %callback; $callback{'OpenParen'} = sub { $self->_close_bundle(@bundle); @bundle = (); - $self->_OpenParen + $self->_OpenParen; + $depth++; + push @$_, '(' foreach values %sub_tree; }; $callback{'CloseParen'} = sub { $self->_close_bundle(@bundle); @bundle = (); $self->_CloseParen; + $depth--; + foreach my $list ( values %sub_tree ) { + if ( $list->[-1] eq '(' ) { + pop @$list; + pop @$list if $list->[-1] =~ /^(?:AND|OR)$/i; + } + else { + pop @$list while $list->[-2] ne '('; + $list->[-1] = pop @$list; + } + } + }; + $callback{'EntryAggregator'} = sub { + $ea = $_[0] || ''; + push @$_, $ea foreach grep @$_ && $_->[-1] ne '(', values %sub_tree; }; - $callback{'EntryAggregator'} = sub { $ea = $_[0] || '' }; $callback{'Condition'} = sub { my ($key, $op, $value) = @_; + my ($negative_op, $null_op, $inv_op, $range_op) + = $self->ClassifySQLOperation( $op ); # key has dot then it's compound variant and we have subkey my $subkey = ''; ($key, $subkey) = ($1, $2) if $key =~ /^([^\.]+)\.(.+)$/; # normalize key and get class (type) my $class; - if (exists $lcfields{lc $key}) { - $key = $lcfields{lc $key}; + if (exists $LOWER_CASE_FIELDS{lc $key}) { + $key = $LOWER_CASE_FIELDS{lc $key}; $class = $FIELD_METADATA{$key}->[0]; } die "Unknown field '$key' in '$string'" unless $class; @@ -228,10 +275,28 @@ sub _parser { } else { $self->_close_bundle(@bundle); @bundle = (); - $sub->( $self, $key, $op, $value, + my @res; my $bundle_with; + if ( $class eq 'WATCHERFIELD' && $key ne 'Owner' && !$negative_op && (!$null_op || $subkey) ) { + if ( !$sub_tree{$key} ) { + $sub_tree{$key} = [ ('(')x$depth, \@res ]; + } else { + $bundle_with = $self->_check_bundling_possibility( $string, @{ $sub_tree{$key} } ); + if ( $sub_tree{$key}[-1] eq '(' ) { + push @{ $sub_tree{$key} }, \@res; + } + } + } + + # Remove our aggregator from subtrees where our condition didn't get added + pop @$_ foreach grep @$_ && $_->[-1] =~ /^(?:AND|OR)$/i, values %sub_tree; + + # A reference to @res may be pushed onto $sub_tree{$key} from + # above, and we fill it here. + @res = $sub->( $self, $key, $op, $value, SUBCLAUSE => '', # don't need anymore ENTRYAGGREGATOR => $ea, SUBKEY => $subkey, + BUNDLE => $bundle_with, ); } $self->{_sql_looking_at}{lc $key} = 1; @@ -241,6 +306,29 @@ sub _parser { $self->_close_bundle(@bundle); @bundle = (); } +sub _check_bundling_possibility { + my $self = shift; + my $string = shift; + my @list = reverse @_; + while (my $e = shift @list) { + next if $e eq '('; + if ( lc($e) eq 'and' ) { + return undef; + } + elsif ( lc($e) eq 'or' ) { + return shift @list; + } + else { + # should not happen + $RT::Logger->error( + "Joins optimization failed when parsing '$string'. It's bug in RT, contact Best Practical" + ); + die "Internal error. Contact your system administrator."; + } + } + return undef; +} + =head2 ClausesToSQL =cut @@ -295,8 +383,9 @@ sub FromSQL { $self->{_sql_query} = $query; eval { $self->_parser( $query ); }; if ( $@ ) { - $RT::Logger->error( $@ ); - return (0, $@); + my $error = "$@"; + $RT::Logger->error("Couldn't parse query: $error"); + return (0, $error); } # We only want to look at EffectiveId's (mostly) for these searches.