[Rt-commit] rt branch, 4.2/search-active-inactive-status, created. rt-4.2.11-8-g3f7258d

? sunnavy sunnavy at bestpractical.com
Sun May 24 11:54:12 EDT 2015


The branch, 4.2/search-active-inactive-status has been created
        at  3f7258d0e6f6788d987f3f9a363c7c812a7f5b59 (commit)

- Log -----------------------------------------------------------------
commit 25bba4bd533944f4311371188f65d7fa2daed374
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jan 21 20:44:50 2015 -0500

    Temporarily remove bundling code; it will be replaced by an optimizer

diff --git a/lib/RT/SearchBuilder/Role/Roles.pm b/lib/RT/SearchBuilder/Role/Roles.pm
index 914c74b..b8c868d 100644
--- a/lib/RT/SearchBuilder/Role/Roles.pm
+++ b/lib/RT/SearchBuilder/Role/Roles.pm
@@ -262,11 +262,7 @@ sub RoleLimit {
     $args{FIELD} ||= 'EmailAddress';
 
     my ($groups, $group_members, $users);
-    if ( $args{'BUNDLE'} ) {
-        ($groups, $group_members, $users) = @{ $args{'BUNDLE'} };
-    } else {
-        $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type );
-    }
+    $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type );
 
     $self->_OpenParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
     if ( $args{OPERATOR} =~ /^IS(?: NOT)?$/i ) {
@@ -393,7 +389,6 @@ sub RoleLimit {
         }
     }
     $self->_CloseParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
-    return ($groups, $group_members, $users);
 }
 
 1;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index c641cd2..992075e 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -2930,64 +2930,19 @@ sub _parser {
     my ($self,$string) = @_;
     my $ea = '';
 
-    # Bundling of joins is implemented by dynamically tracking a parallel query
-    # tree in %sub_tree as the TicketSQL is parsed.
-    #
-    # 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->_OpenParen;
-      $depth++;
-      push @$_, '(' foreach values %sub_tree;
     };
     $callback{'CloseParen'} = sub {
       $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{'Condition'} = sub {
         my ($key, $op, $value) = @_;
 
-        my $negative_op = ($op eq '!=' || $op =~ /\bNOT\b/i);
-        my $null_op = ( 'is not' eq lc($op) || 'is' eq lc($op) );
         # key has dot then it's compound variant and we have subkey
         my $subkey = '';
         ($key, $subkey) = ($1, $2) if $key =~ /^([^\.]+)\.(.+)$/;
@@ -3009,28 +2964,12 @@ sub _parser {
         }
         my $sub = $dispatch{ $class };
 
-        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,
+        $sub->( $self, $key, $op, $value,
                 SUBCLAUSE       => '',  # don't need anymore
                 ENTRYAGGREGATOR => $ea,
                 SUBKEY          => $subkey,
-                BUNDLE          => $bundle_with,
               );
         $ea = '';
     };
@@ -3102,29 +3041,6 @@ sub Query {
     return $self->{_sql_query};
 }
 
-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;
-}
-
 RT::Base->_ImportOverlays();
 
 1;

commit b68c84f0c915e558cbd127f94ef7740c1ec3c359
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jan 21 21:37:21 2015 -0500

    Switch to parsing into a parse tree as an IR

diff --git a/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/lib/RT/Interface/Web/QueryBuilder/Tree.pm
index d7de61c..4c8f11c 100644
--- a/lib/RT/Interface/Web/QueryBuilder/Tree.pm
+++ b/lib/RT/Interface/Web/QueryBuilder/Tree.pm
@@ -113,7 +113,7 @@ sub GetReferencedQueues {
             return unless $clause->{Key} eq 'Queue';
             return unless $clause->{Op} eq '=';
 
-            $queues->{ $clause->{RawValue} } = 1;
+            $queues->{ $clause->{Value} } = 1;
         }
     );
 
@@ -207,10 +207,20 @@ sub __LinearizeTree {
         } else {
 
             my $clause = $node->getNodeValue;
-            $str .= $clause->{Key};
-            $str .= " ". $clause->{Op};
-            $str .= " ". $clause->{Value};
-
+            my $key = $clause->{Key};
+            $key .= "." . $clause->{Subkey} if defined $clause->{Subkey};
+            if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) {
+                $key = "'$key'";
+            }
+            my $value = $clause->{Value};
+            if ( $clause->{Op} =~ /^IS( NOT)?$/i ) {
+                $value = 'NULL';
+            } elsif ( $value !~ /^[+-]?[0-9]+$/ ) {
+                $value =~ s/(['\\])/\\$1/g;
+                $value = "'$value'";
+            }
+
+            $str .= $key ." ". $clause->{Op} . " " . $value;
         }
         $str =~ s/^\s+|\s+$//;
 
@@ -255,32 +265,20 @@ sub ParseSQL {
     $callback{'EntryAggregator'} = sub { $node->setNodeValue( $_[0] ) };
     $callback{'Condition'} = sub {
         my ($key, $op, $value) = @_;
-        my $rawvalue = $value;
 
-        my ($main_key) = split /[.]/, $key;
+        my ($main_key, $subkey) = split /[.]/, $key, 2;
 
-        my $class;
-        if ( exists $lcfield{ lc $main_key } ) {
-            $key =~ s/^[^.]+/ $lcfield{ lc $main_key } /e;
-            ($main_key) = split /[.]/, $key;  # make the case right
-            $class = $field{ $main_key }->[0];
-        }
-        unless( $class ) {
+        unless( $lcfield{ lc $main_key} ) {
             push @results, [ $args{'CurrentUser'}->loc("Unknown field: [_1]", $key), -1 ]
         }
+        $main_key = $lcfield{ lc $main_key };
 
-        if ( lc $op eq 'is' || lc $op eq 'is not' ) {
-            $value = 'NULL'; # just fix possible mistakes here
-        } elsif ( $value !~ /^[+-]?[0-9]+$/ ) {
-            $value =~ s/(['\\])/\\$1/g;
-            $value = "'$value'";
-        }
-
-        if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) {
-            $key = "'$key'";
-        }
+        # Hardcode value for IS / IS NOT
+        $value = 'NULL' if $op =~ /^IS( NOT)?$/i;
 
-        my $clause = { Key => $key, Op => $op, Value => $value, RawValue => $rawvalue };
+        my $clause = { Key => $main_key, Subkey => $subkey,
+                       Meta => $field{ $main_key },
+                       Op => $op, Value => $value };
         $node->addChild( __PACKAGE__->new( $clause ) );
     };
     $callback{'Error'} = sub { push @results, @_ };
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 992075e..98f2d71 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -2928,52 +2928,45 @@ failure.
 
 sub _parser {
     my ($self,$string) = @_;
+
+    require RT::Interface::Web::QueryBuilder::Tree;
+    my $tree = RT::Interface::Web::QueryBuilder::Tree->new;
+    $tree->ParseSQL(
+        Query => $string,
+        CurrentUser => $self->CurrentUser,
+    );
+
     my $ea = '';
+    $tree->traverse(
+        sub {
+            my $node = shift;
+            $ea = $node->getParent->getNodeValue if $node->getIndex > 0;
+            return $self->_OpenParen unless $node->isLeaf;
 
-    my %callback;
-    $callback{'OpenParen'} = sub {
-      $self->_OpenParen;
-    };
-    $callback{'CloseParen'} = sub {
-      $self->_CloseParen;
-    };
-    $callback{'EntryAggregator'} = sub {
-      $ea = $_[0] || '';
-    };
-    $callback{'Condition'} = sub {
-        my ($key, $op, $value) = @_;
-
-        # 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 $LOWER_CASE_FIELDS{lc $key}) {
-            $key = $LOWER_CASE_FIELDS{lc $key};
-            $class = $FIELD_METADATA{$key}->[0];
-        }
-        die "Unknown field '$key' in '$string'" unless $class;
+            my ($key, $subkey, $meta, $op, $value)
+                = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value/};
 
-        # replace __CurrentUser__ with id
-        $value = $self->CurrentUser->id if $value eq '__CurrentUser__';
+            # normalize key and get class (type)
+            my $class = $meta->[0];
 
+            # replace __CurrentUser__ with id
+            $value = $self->CurrentUser->id if $value eq '__CurrentUser__';
 
-        unless( $dispatch{ $class } ) {
-            die "No dispatch method for class '$class'"
-        }
-        my $sub = $dispatch{ $class };
+            my $sub = $dispatch{ $class }
+                or die "No dispatch method for class '$class'";
 
-        # A reference to @res may be pushed onto $sub_tree{$key} from
-        # above, and we fill it here.
-        $sub->( $self, $key, $op, $value,
-                SUBCLAUSE       => '',  # don't need anymore
-                ENTRYAGGREGATOR => $ea,
-                SUBKEY          => $subkey,
-              );
-        $ea = '';
-    };
-    RT::SQL::Parse($string, \%callback);
+            # A reference to @res may be pushed onto $sub_tree{$key} from
+            # above, and we fill it here.
+            $sub->( $self, $key, $op, $value,
+                    ENTRYAGGREGATOR => $ea,
+                    SUBKEY          => $subkey,
+                  );
+        },
+        sub {
+            my $node = shift;
+            return $self->_CloseParen unless $node->isLeaf;
+        }
+    );
 }
 
 sub FromSQL {
diff --git a/share/html/Search/Build.html b/share/html/Search/Build.html
index e5ba586..31b5487 100644
--- a/share/html/Search/Build.html
+++ b/share/html/Search/Build.html
@@ -230,30 +230,11 @@ foreach my $arg ( keys %ARGS ) {
     for ( my $i = 0; $i < @ops; $i++ ) {
         my ( $op, $value ) = ( $ops[$i], $values[$i] );
         next if !defined $value || $value eq '';
-        my $rawvalue = $value;
-
-        if ( $value =~ /^NULL$/i && $op =~ /=/ ) {
-            if ( $op eq '=' ) {
-                $op = "IS";
-            }
-            elsif ( $op eq '!=' ) {
-                $op = "IS NOT";
-            }
-        }
-        elsif ($value =~ /\D/) {
-            $value =~ s/(['\\])/\\$1/g;
-            $value = "'$value'";
-        }
-
-        if ($keyword =~ s/(['\\])/\\$1/g or $keyword =~ /[^{}\w\.]/) {
-            $keyword = "'$keyword'";
-        }
 
         my $clause = {
             Key   => $keyword,
             Op    => $op,
             Value => $value,
-            RawValue => $rawvalue,
         };
 
         push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);

commit 9541bb94e83a97f03f29e73970d0dad86adb6764
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jan 21 22:30:44 2015 -0500

    Bundling as an optimization pass

diff --git a/lib/RT/SearchBuilder/Role/Roles.pm b/lib/RT/SearchBuilder/Role/Roles.pm
index b8c868d..134a507 100644
--- a/lib/RT/SearchBuilder/Role/Roles.pm
+++ b/lib/RT/SearchBuilder/Role/Roles.pm
@@ -262,7 +262,11 @@ sub RoleLimit {
     $args{FIELD} ||= 'EmailAddress';
 
     my ($groups, $group_members, $users);
-    $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type );
+    if ( $args{'BUNDLE'} and @{$args{'BUNDLE'}}) {
+        ($groups, $group_members, $users) = @{ $args{'BUNDLE'} };
+    } else {
+        $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type );
+    }
 
     $self->_OpenParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
     if ( $args{OPERATOR} =~ /^IS(?: NOT)?$/i ) {
@@ -389,6 +393,9 @@ sub RoleLimit {
         }
     }
     $self->_CloseParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
+    if ($args{BUNDLE} and not @{$args{BUNDLE}}) {
+        @{$args{BUNDLE}} = ($groups, $group_members, $users);
+    }
 }
 
 1;
diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index 98f2d71..e9d856f 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -2936,6 +2936,27 @@ sub _parser {
         CurrentUser => $self->CurrentUser,
     );
 
+    # Perform an optimization pass looking for watcher bundling
+    $tree->traverse(
+        sub {
+            my $node = shift;
+            return if $node->isLeaf;
+            return unless ($node->getNodeValue||'') eq "OR";
+            my %refs;
+            my @kids = grep {$_->{Meta}[0] eq "WATCHERFIELD"}
+                map {$_->getNodeValue}
+                grep {$_->isLeaf} $node->getAllChildren;
+            for (@kids) {
+                my $node = $_;
+                my ($key, $subkey, $op) = @{$node}{qw/Key Subkey Op/};
+                next if $node->{Meta}[1] and RT::Ticket->Role($node->{Meta}[1])->{Column};
+                next if $op =~ /^!=$|\bNOT\b/i;
+                next if $op =~ /^IS( NOT)?$/i and not $subkey;
+                $node->{Bundle} = $refs{$node->{Meta}[1] || ''} ||= [];
+            }
+        }
+    );
+
     my $ea = '';
     $tree->traverse(
         sub {
@@ -2943,8 +2964,8 @@ sub _parser {
             $ea = $node->getParent->getNodeValue if $node->getIndex > 0;
             return $self->_OpenParen unless $node->isLeaf;
 
-            my ($key, $subkey, $meta, $op, $value)
-                = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value/};
+            my ($key, $subkey, $meta, $op, $value, $bundle)
+                = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/};
 
             # normalize key and get class (type)
             my $class = $meta->[0];
@@ -2960,6 +2981,7 @@ sub _parser {
             $sub->( $self, $key, $op, $value,
                     ENTRYAGGREGATOR => $ea,
                     SUBKEY          => $subkey,
+                    BUNDLE          => $bundle,
                   );
         },
         sub {

commit 3f7258d0e6f6788d987f3f9a363c7c812a7f5b59
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Sun May 24 23:45:51 2015 +0800

    support __Active__ and __Inactive__ status in ticket sql

diff --git a/lib/RT/Tickets.pm b/lib/RT/Tickets.pm
index e9d856f..392c208 100644
--- a/lib/RT/Tickets.pm
+++ b/lib/RT/Tickets.pm
@@ -76,7 +76,7 @@ use Role::Basic 'with';
 with 'RT::SearchBuilder::Role::Roles';
 
 use Scalar::Util qw/blessed/;
-
+use 5.010;
 use RT::Ticket;
 use RT::SQL;
 
@@ -2936,6 +2936,93 @@ sub _parser {
         CurrentUser => $self->CurrentUser,
     );
 
+    state ( $active_status_node, $inactive_status_node );
+
+    $tree->traverse(
+        sub {
+            my $node = shift;
+            return unless $node->isLeaf and $node->getNodeValue;
+            my ($key, $subkey, $meta, $op, $value, $bundle)
+                = @{$node->getNodeValue}{qw/Key Subkey Meta Op Value Bundle/};
+            return unless $key eq "Status" && $value =~ /^(?:__(?:in)?active__)$/i;
+
+            my $parent = $node->getParent;
+            my $index = $node->getIndex;
+
+            if ( ( lc $value eq '__inactive__' && $op eq '=' ) || ( lc $value eq '__active__' && $op eq '!=' ) ) {
+                unless ( $inactive_status_node ) {
+                    my %lifecycle =
+                      map { $_ => $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } }
+                      grep { @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ inactive } || [] } }
+                      keys %RT::Lifecycle::LIFECYCLES;
+                    return unless %lifecycle;
+
+                    my $sql;
+                    if ( keys %lifecycle == 1 ) {
+                        $sql = join ' OR ', map { qq{ Status = "$_" } } map { @$_ } values %lifecycle;
+                    }
+                    else {
+                        my @inactive_sql;
+                        for my $name ( keys %lifecycle ) {
+                            my $inactive_sql =
+                                qq{Lifecycle = "$name"}
+                              . ' AND ('
+                              . join( ' OR ', map { qq{ Status = "$_" } } @{ $lifecycle{ $name } } ) . ')';
+                            push @inactive_sql, qq{($inactive_sql)};
+                        }
+                        $sql = join ' OR ', @inactive_sql;
+                    }
+                    $inactive_status_node = RT::Interface::Web::QueryBuilder::Tree->new;
+                    $inactive_status_node->ParseSQL(
+                        Query       => $sql,
+                        CurrentUser => $self->CurrentUser,
+                    );
+                }
+                $parent->removeChild( $node );
+                $parent->insertChild( $index, $inactive_status_node );
+            }
+            else {
+                unless ( $active_status_node ) {
+                    my %lifecycle =
+                      map {
+                        $_ => [
+                            @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] },
+                            @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active }  || [] },
+                          ]
+                      }
+                      grep {
+                             @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ initial } || [] }
+                          || @{ $RT::Lifecycle::LIFECYCLES{ $_ }{ active }  || [] }
+                      } keys %RT::Lifecycle::LIFECYCLES;
+                    return unless %lifecycle;
+
+                    my $sql;
+                    if ( keys %lifecycle == 1 ) {
+                        $sql = join ' OR ', map { qq{ Status = "$_" } } map { @$_ } values %lifecycle;
+                    }
+                    else {
+                        my @active_sql;
+                        for my $name ( keys %lifecycle ) {
+                            my $active_sql =
+                                qq{Lifecycle = "$name"}
+                              . ' AND ('
+                              . join( ' OR ', map { qq{ Status = "$_" } } @{ $lifecycle{ $name } } ) . ')';
+                            push @active_sql, qq{($active_sql)};
+                        }
+                        $sql = join ' OR ', @active_sql;
+                    }
+                    $active_status_node = RT::Interface::Web::QueryBuilder::Tree->new;
+                    $active_status_node->ParseSQL(
+                        Query       => $sql,
+                        CurrentUser => $self->CurrentUser,
+                    );
+                }
+                $parent->removeChild( $node );
+                $parent->insertChild( $index, $active_status_node );
+            }
+        }
+    );
+
     # Perform an optimization pass looking for watcher bundling
     $tree->traverse(
         sub {

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


More information about the rt-commit mailing list