[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