[rt-users] Huge Attachments table in 3.4.4

Jesse Vincent jesse at bestpractical.com
Fri Oct 28 08:41:17 EDT 2005


Luke,

Are you up for trying the patch we've been testing for this? If so,
please see attached



On Fri, Oct 28, 2005 at 04:41:05PM +0930, Luke Vanderfluit wrote:
> Hi.
> 
> Ok. I'm now at the point where:
> -(I'm about to fetch the axe from the shed :-)
> -I've removed all indexes from the Groups table
> -created the following indexes:
> 
> create unique index groups_key on Groups(id);
> create index Groups1 ON Groups (Domain, Instance, Type, id);
> create index Groups2 ON Groups (Type, Instance, Domain);
> 
> Then I did a trial run: 138 seconds to complete the query
> 
> I created 2 more indexes
> 
> create index Groups3 on Groups(Instance);
> create index Groups4 on Groups(Type);
> 
> Another trial run: 137 seconds.
> 
> I created the following index:
> create index Groups5 on Groups(Domain);
> 
> 144 seconds.
> 
> The query in question is the same as on the following site and the 
> indexes are taken from that site.
> http://rt3.fsck.com/Ticket/Display.html?id=6624
> 
> after dropping the last index I added (it seemed to only worsen the pain) I
> stopped and started both mysql and apache
> then got 150 seconds.
> hmmm.....
> 
> So these particular indexes didn't help.
> 
> I've used the expain select syntax to show what keys are being used in 
> the query, it show that I have *no* keys being used on the Users table. 
> I've tried several keys on the Users table but with no success.
> I've documented everything so can supply you with the indexes if interested.
> 
> 
> I'm sure there is an index out there that does what I want,
> Any suggestions/help appreciated.
> 
> Thanks.
> Kind regards.
> Luke.
> 
> -- 
> Luke
> 
> _______________________________________________
> http://lists.bestpractical.com/cgi-bin/mailman/listinfo/rt-users
> 
> Be sure to check out the RT Wiki at http://wiki.bestpractical.com
> 
> Buy your copy of our new book, RT Essentials, today! 
> 
> Download a free sample chapter from http://rtbook.bestpractical.com
> 

-- 
-------------- next part --------------
=== lib/RT/Groups_Overlay.pm
==================================================================
--- lib/RT/Groups_Overlay.pm	(revision 1155)
+++ lib/RT/Groups_Overlay.pm	(local)
@@ -85,12 +85,21 @@
   $self->{'table'} = "Groups";
   $self->{'primary_key'} = "id";
 
+  my @result = $self->SUPER::_Init(@_);
+
   $self->OrderBy( ALIAS => 'main',
 		  FIELD => 'Name',
 		  ORDER => 'ASC');
 
+  $self->{'princalias'} = $self->NewAlias('Principals');
 
-  return ( $self->SUPER::_Init(@_));
+  $self->Join( ALIAS1 => 'main',
+               FIELD1 => 'id',
+               ALIAS2 => $self->{'princalias'},
+               FIELD2 => 'id' );
+
+
+  return (@result);
 }
 # }}}
 
@@ -343,83 +352,51 @@
 
 =cut
 
+sub PrincipalsAlias {
+    my $self = shift;
+    return($self->{'princalias'});
 
+}
+
 sub WithRight {
     my $self = shift;
     my %args = ( Right                  => undef,
                  Object =>              => undef,
                  IncludeSystemRights    => 1,
                  IncludeSuperusers      => undef,
+                 IncludeSubgroupMembers => 0,
                  EquivObjects           => [ ],
                  @_ );
 
-    my $acl        = $self->NewAlias('ACL');
+    my $from_role = $self->Clone;
+    $from_role->WithRoleRight( %args );
 
-    # {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser 
-    $self->Limit( ALIAS           => $acl,
-                  FIELD           => 'RightName',
-                  OPERATOR        => ($args{Right} ? '=' : 'IS NOT'),
-                  VALUE           => $args{Right} || 'NULL',
-                  ENTRYAGGREGATOR => 'OR' );
+    my $from_group = $self->Clone;
+    $from_group->WithGroupRight( %args );
 
-    if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
-        $self->Limit( ALIAS           => $acl,
-                      FIELD           => 'RightName',
-                      OPERATOR        => '=',
-                      VALUE           => 'SuperUser',
-                      ENTRYAGGREGATOR => 'OR' );
-    }
-    # }}}
+    #XXX: DIRTY HACK
+    use DBIx::SearchBuilder::Union;
+    my $union = new DBIx::SearchBuilder::Union;
+    $union->add($from_role);
+    $union->add($from_group);
+    %$self = %$union;
+    bless $self, ref($union);
 
-    my ($or_check_ticket_roles, $or_check_roles);
-    my $which_object = "$acl.ObjectType = 'RT::System'";
+    return;
+}
 
-    if ( defined $args{'Object'} ) {
-        if ( ref($args{'Object'}) eq 'RT::Ticket' ) {
-            $or_check_ticket_roles =
-                " OR ( main.Domain = 'RT::Ticket-Role' AND main.Instance = " . $args{'Object'}->Id . ") ";
-
-            # If we're looking at ticket rights, we also want to look at the associated queue rights.
-            # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
-            # we load the queue object and ask all the rest of our questions about the queue.
-            $args{'Object'}   = $args{'Object'}->QueueObj;
-        }
-        # TODO XXX This really wants some refactoring
-        if ( ref($args{'Object'}) eq 'RT::Queue' ) {
-            $or_check_roles =
-                " OR ( ( (main.Domain = 'RT::Queue-Role' AND main.Instance = " .
-                $args{'Object'}->Id . ") $or_check_ticket_roles ) " .
-                " AND main.Type = $acl.PrincipalType) ";
-        }
-
-	if ( $args{'IncludeSystemRights'} ) {
-	    $which_object .= ' OR ';
-	}
-	else {
-	    $which_object = '';
-	}
-        foreach my $obj ( @{ $args{'EquivObjects'} } ) {
-             next unless ( UNIVERSAL::can( $obj, 'id' ) );
-             $which_object .= "($acl.ObjectType = '" . ref( $obj ) . "' AND $acl.ObjectId = " . $obj->id . ") OR ";
-         }
-        $which_object .=
-            " ($acl.ObjectType = '" . ref($args{'Object'}) . "'" .
-            " AND $acl.ObjectId = " . $args{'Object'}->Id . ") ";
-    }
-
-    $self->_AddSubClause( "WhichObject", "($which_object)" );
-
-    $self->_AddSubClause( "WhichGroup",
-        qq{
-          ( (    $acl.PrincipalId = main.id
-             AND $acl.PrincipalType = 'Group'
-             AND (   main.Domain = 'SystemInternal'
-                  OR main.Domain = 'UserDefined'
-                  OR main.Domain = 'ACLEquivalence'))
-           $or_check_roles)
-        }
-    );
+use RT::Users;
+sub _JoinGroups {
+    my $self = shift;
+    my %args = (@_);
+    return 'main' unless $args{'IncludeSubgroupMembers'};
+    return $self->RT::Users::_JoinGroupMembers( %args );
 }
+sub _JoinGroupMembers { return (shift)->RT::Users::_JoinGroupMembers( @_ ) }
+sub _JoinACL          { return (shift)->RT::Users::_JoinACL( @_ ) }
+sub _GetEquivObjects  { return (shift)->RT::Users::_GetEquivObjects( @_ ) }
+sub WithGroupRight    { return (shift)->RT::Users::WhoHaveGroupRight( @_ ) }
+sub WithRoleRight     { return (shift)->RT::Users::WhoHaveRoleRight( @_ ) }
 
 # {{{ sub LimitToEnabled
 
@@ -432,18 +409,11 @@
 sub LimitToEnabled {
     my $self = shift;
     
-    my $alias = $self->Join(
-	TYPE   => 'left',
-	ALIAS1 => 'main',
-	FIELD1 => 'id',
-	TABLE2 => 'Principals',
-	FIELD2 => 'id'
-    );
-
-    $self->Limit( ALIAS => $alias,
-		  FIELD => 'Disabled',
-		  VALUE => '0',
-		  OPERATOR => '=' );
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+		          FIELD => 'Disabled',
+		          VALUE => '0',
+		          OPERATOR => '=',
+                );
 }
 # }}}
 
@@ -458,20 +428,12 @@
 sub LimitToDeleted {
     my $self = shift;
     
-    my $alias = $self->Join(
-	TYPE   => 'left',
-	ALIAS1 => 'main',
-	FIELD1 => 'id',
-	TABLE2 => 'Principals',
-	FIELD2 => 'id'
-    );
-
     $self->{'find_disabled_rows'} = 1;
-    $self->Limit( ALIAS => $alias,
-		  FIELD => 'Disabled',
-		  OPERATOR => '=',
-		  VALUE => '1'
-		);
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'Disabled',
+                  OPERATOR => '=',
+                  VALUE => 1,
+                );
 }
 # }}}
 
=== lib/RT/Principal_Overlay.pm
==================================================================
--- lib/RT/Principal_Overlay.pm	(revision 1155)
+++ lib/RT/Principal_Overlay.pm	(local)
@@ -295,38 +295,39 @@
         Right        => undef,
         Object       => undef,
         EquivObjects => undef,
-        @_
+        @_,
     );
 
+    unless ( $args{'Right'} ) {
+        $RT::Logger->crit("HasRight called without a right");
+        return (undef);
+    }
+
     if ( $self->Disabled ) {
-        $RT::Logger->err( "Disabled User:  "
+        $RT::Logger->error( "Disabled User:  "
               . $self->id
               . " failed access check for "
               . $args{'Right'} );
         return (undef);
     }
 
-    if ( !defined $args{'Right'} ) {
-        $RT::Logger->crit("HasRight called without a right");
-        return (undef);
-    }
-
     if (   defined( $args{'Object'} )
         && UNIVERSAL::can( $args{'Object'}, 'id' )
-        && $args{'Object'}->id )
-    {
+        && $args{'Object'}->id ) {
+
         push( @{ $args{'EquivObjects'} }, $args{Object} );
     }
     else {
-        $RT::Logger->crit("$self HasRight called with no valid object");
+        $RT::Logger->crit("HasRight called with no valid object");
         return (undef);
     }
 
     # If this object is a ticket, we care about ticket roles and queue roles
-    if ( ( ref( $args{'Object'} ) eq 'RT::Ticket' ) && $args{'Object'}->Id ) {
+    if ( UNIVERSAL::isa( $args{'Object'} => 'RT::Ticket' ) ) {
 
-# this is a little bit hacky, but basically, now that we've done the ticket roles magic, we load the queue object
-# and ask all the rest of our questions about the queue.
+        # this is a little bit hacky, but basically, now that we've done
+        # the ticket roles magic, we load the queue object
+        # and ask all the rest of our questions about the queue.
         push( @{ $args{'EquivObjects'} }, $args{'Object'}->QueueObj );
 
     }
@@ -354,167 +355,120 @@
 
     # }}}
 
-    # {{{ if we've cached a positive result for this query, return 1
-
-    my $cached_answer = $_ACL_CACHE->fetch($hashkey);
-
     # Returns undef on cache miss
+    my $cached_answer = $_ACL_CACHE->fetch($hashkey);
     if ( defined $cached_answer ) {
         if ( $cached_answer == 1 ) {
             return (1);
         }
         elsif ( $cached_answer == -1 ) {
-            return (0);
+            return (undef);
         }
     }
 
+    my $hitcount = $self->_HasRight( %args );
+
+    $_ACL_CACHE->set( $hashkey => $hitcount? 1:-1 );
+    return ($hitcount);
+}
+
+sub _HasRight
+{
+    my $self = shift;
+    my %args = (
+        Right        => undef,
+        Object       => undef,
+        EquivObjects => [],
+        @_
+    );
+
     my ( $or_look_at_object_rights, $or_check_roles );
     my $right = $args{'Right'};
+    my @objects = @{ $args{'EquivObjects'} };
 
-    # {{{ Construct Right Match
-
     # If an object is defined, we want to look at rights for that object
 
-    my @look_at_objects;
-    push( @look_at_objects, "ACL.ObjectType = 'RT::System'" )
+    push( @objects, 'RT::System' )
       unless $self->can('_IsOverrideGlobalACL')
       and $self->_IsOverrideGlobalACL( $args{Object} );
 
-    foreach my $obj ( @{ $args{'EquivObjects'} } ) {
-        next unless ( UNIVERSAL::can( $obj, 'id' ) );
-        my $type = ref($obj);
-        my $id   = $obj->id;
+    my ($check_roles, $check_objects) = ('','');
+    if( @objects ) {
+        my @role_clauses;
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
 
-        unless ($id) {
-            use Carp;
-            Carp::cluck(
-                "Trying to check $type rights for an unspecified $type");
-            $RT::Logger->crit(
-                "Trying to check $type rights for an unspecified $type");
+            my $role_clause = "Groups.Domain = '$type-Role'";
+            # XXX: Groups.Instance is VARCHAR in DB, we should quote value
+            # if we want mysql 4.0 use indexes here. we MUST convert that
+            # field to integer and drop this quotes.
+            $role_clause   .= " AND Groups.Instance = '$id'" if $id;
+            push @role_clauses, "($role_clause)";
+
+            my $object_clause = "ACL.ObjectType = '$type'";
+            $object_clause   .= " AND ACL.ObjectId = $id" if $id;
+            push @object_clauses, "($object_clause)";
         }
-        push @look_at_objects,
-          "(ACL.ObjectType = '$type' AND ACL.ObjectId = '$id')";
+
+        $check_roles .= join ' OR ', @role_clauses;
+        $check_objects = join ' OR ', @object_clauses;
     }
 
-    # }}}
-
-    # {{{ Build that honkin-big SQL query
-
     my $query_base =
       "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE  " .
 
       # Only find superuser or rights with the name $right
-      "(ACL.RightName = 'SuperUser' OR  ACL.RightName = '$right') " .
+      "(ACL.RightName = 'SuperUser' OR  ACL.RightName = '$right') "
 
       # Never find disabled groups.
-      "AND Principals.Disabled = 0 "
-      . "AND CachedGroupMembers.Disabled = 0  "
+      . "AND Principals.Disabled = 0 "
+      . "AND CachedGroupMembers.Disabled = 0 "
+
+      # We always grant rights to Groups
       . "AND Principals.id = Groups.id "
-      .    # We always grant rights to Groups
+      . "AND Principals.PrincipalType = 'Group' "
 
-# See if the principal is a member of the group recursively or _is the rightholder_
-# never find recursively disabled group members
-# also, check to see if the right is being granted _directly_ to this principal,
-#  as is the case when we want to look up group rights
-"AND  Principals.id = CachedGroupMembers.GroupId AND CachedGroupMembers.MemberId = '"
-      . $self->Id . "' "
-      .
+      # See if the principal is a member of the group recursively or _is the rightholder_
+      # never find recursively disabled group members
+      # also, check to see if the right is being granted _directly_ to this principal,
+      #  as is the case when we want to look up group rights
+      . "AND Principals.id = CachedGroupMembers.GroupId "
+      . "AND CachedGroupMembers.MemberId = ". $self->Id ." "
 
-  # Make sure the rights apply to the entire system or to the object in question
-      "AND ( " . join( ' OR ', @look_at_objects ) . ") ";
+      # Make sure the rights apply to the entire system or to the object in question
+      . "AND ($check_objects) ";
 
-# The groups query does the query based on group membership and individual user rights
+    # The groups query does the query based on group membership and individual user rights
+    my $groups_query = $query_base
+      # limit the result set to groups of types ACLEquivalence (user),
+      # UserDefined, SystemInternal and Personal. All this we do
+      # via (ACL.PrincipalType = 'Group') condition
+      . "AND ACL.PrincipalId = Principals.id "
+      . "AND ACL.PrincipalType = 'Group' ";
 
-    my $groups_query = $query_base .
+    $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result
+    my $hitcount = $self->_Handle->FetchResult($groups_query);
+    return 1 if $hitcount; # get out of here if success
 
-# limit the result set to groups of types ACLEquivalence (user)  UserDefined, SystemInternal and Personal
-"AND ( (  ACL.PrincipalId = Principals.id AND ACL.PrincipalType = 'Group' AND "
-      . "(Groups.Domain = 'SystemInternal' OR Groups.Domain = 'UserDefined' OR Groups.Domain = 'ACLEquivalence' OR Groups.Domain = 'Personal'))"
-      .
-
-      " ) ";
-    $self->_Handle->ApplyLimits( \$groups_query, 1 );    #only return one result
-
-    my @roles;
-    foreach my $object ( @{ $args{'EquivObjects'} } ) {
-        push( @roles, $self->_RolesForObject( ref($object), $object->id ) );
-    }
-
     # The roles query does the query based on roles
-    my $roles_query;
-    if (@roles) {
-        $roles_query =
-            $query_base . "AND " . " ( ("
-          . join( ' OR ', @roles ) . " ) "
-          . " AND Groups.Type = ACL.PrincipalType AND Groups.Id = Principals.id AND Principals.PrincipalType = 'Group') ";
-        $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result
+    my $roles_query = $query_base
+      . "AND ACL.PrincipalType = Groups.Type "
+      . "AND ($check_roles) ";
+    $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result
 
-    }
-
-    # }}}
-
-    # {{{ Actually check the ACL by performing an SQL query
-    #   $RT::Logger->debug("Now Trying $groups_query");
-    my $hitcount = $self->_Handle->FetchResult($groups_query);
-
-    # }}}
-
-    # {{{ if there's a match, the right is granted
-    if ($hitcount) {
-        $_ACL_CACHE->set( $hashkey => 1 );
-        return (1);
-    }
-
-    # Now check the roles query
     $hitcount = $self->_Handle->FetchResult($roles_query);
+    return 1 if $hitcount; # get out of here if success
 
-    if ($hitcount) {
-        $_ACL_CACHE->set( $hashkey => 1 );
-        return (1);
-    }
-
-    # We failed to find an acl hit
-    $_ACL_CACHE->set( $hashkey => -1 );
-    return (undef);
+    return 0;
 }
 
 # }}}
 
-# {{{ _RolesForObject
-
-
-
-=head2 _RolesForObject( $object_type, $object_id)
-
-Returns an SQL clause finding role groups for Objects
-
-=cut
-
-
-sub _RolesForObject {
-    my $self = shift;
-    my $type = shift;
-    my $id = shift;
-
-    unless ($id) {
-	$id = '0';
-   }
-
-   # This should never be true.
-   unless ($id =~ /^\d+$/) {
-	$RT::Logger->crit("RT::Prinicipal::_RolesForObject called with type $type and a non-integer id: '$id'");
-	$id = "'$id'";
-   }
-
-    my $clause = "(Groups.Domain = '".$type."-Role' AND Groups.Instance = $id) ";
-
-    return($clause);
-}
-
 # }}}
 
-# }}}
-
 # {{{ ACL caching
 
 
@@ -578,6 +532,8 @@
     # just return the value for non-objects
     return $scalar unless UNIVERSAL::can($scalar, 'id');
 
+    return ref($scalar) unless $scalar->id;
+
     # an object -- return the class and id
     return(ref($scalar)."-". $scalar->id);
 }
=== lib/RT/Users_Overlay.pm
==================================================================
--- lib/RT/Users_Overlay.pm	(revision 1155)
+++ lib/RT/Users_Overlay.pm	(local)
@@ -144,7 +144,7 @@
 sub LimitToEnabled {
     my $self = shift;
 
-    $self->Limit( ALIAS    => $self->{'princalias'},
+    $self->Limit( ALIAS    => $self->PrincipalsAlias,
                   FIELD    => 'Disabled',
                   VALUE    => '0',
                   OPERATOR => '=' );
@@ -187,7 +187,7 @@
     my $groupalias = $self->NewAlias('CachedGroupMembers');
 
     # Join the principal to the groups table
-    $self->Join( ALIAS1 => $self->{'princalias'},
+    $self->Join( ALIAS1 => $self->PrincipalsAlias,
                  FIELD1 => 'id',
                  ALIAS2 => $groupalias,
                  FIELD2 => 'MemberId' );
@@ -288,69 +288,72 @@
 
 =end testing
 
-
 find all users who the right Right for this group, either individually
 or as members of groups
 
-
 If passed a queue object, with no id, it will find users who have that right for _any_ queue
 
-
-
 =cut
 
-sub WhoHaveRight {
+sub _JoinGroupMembers
+{
     my $self = shift;
     my %args = (
-        Right                  => undef,
-        Object                 => undef,
-        IncludeSystemRights    => undef,
-        IncludeSuperusers      => undef,
         IncludeSubgroupMembers => 1,
-        EquivObjects           => [ ],
         @_
     );
 
-    if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
-        $RT::Logger->crit( "$self WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
-        return (undef);
-    }
-    
+    my $principals = $self->PrincipalsAlias;
 
-    # Find only members of groups that have the right.
-
-    my $acl       = $self->NewAlias('ACL');
-    my $groups    = $self->NewAlias('Groups');
-    my $userprinc = $self->{'princalias'};
-
-# The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups
-# if we bind to CachedGroupMembers, we'll find all members of groups recursively.
-# if we don't we'll find only 'direct' members of the group in question
-    my $cgm;
-
+    # The cachedgroupmembers table is used for unrolling group memberships
+    # to allow fast lookups. if we bind to CachedGroupMembers, we'll find
+    # all members of groups recursively. if we don't we'll find only 'direct'
+    # members of the group in question
+    my $group_members;
     if ( $args{'IncludeSubgroupMembers'} ) {
-        $cgm = $self->NewAlias('CachedGroupMembers');
+        $group_members = $self->NewAlias('CachedGroupMembers');
     }
     else {
-        $cgm = $self->NewAlias('GroupMembers');
+        $group_members = $self->NewAlias('GroupMembers');
     }
 
-#Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
     $self->Join(
-        ALIAS1 => $cgm,
+        ALIAS1 => $group_members,
         FIELD1 => 'MemberId',
-        ALIAS2 => $userprinc,
+        ALIAS2 => $principals,
         FIELD2 => 'id'
     );
 
+    return $group_members;
+}
+
+sub _JoinGroups
+{
+    my $self = shift;
+    my %args = (@_);
+
+    my $group_members = $self->_JoinGroupMembers( %args );
+    my $groups = $self->NewAlias('Groups');
     $self->Join(
         ALIAS1 => $groups,
         FIELD1 => 'id',
-        ALIAS2 => $cgm,
+        ALIAS2 => $group_members,
         FIELD2 => 'GroupId'
     );
 
-# {{{ Find only rows where the right granted is the one we're looking up or _possibly_ superuser
+    return $groups;
+}
+
+sub _JoinACL
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        IncludeSuperusers      => undef,
+        @_,
+    );
+
+    my $acl = $self->NewAlias('ACL');
     $self->Limit(
         ALIAS    => $acl,
         FIELD    => 'RightName',
@@ -358,7 +361,6 @@
         VALUE => $args{Right} || 'NULL',
         ENTRYAGGREGATOR => 'OR'
     );
-
     if ( $args{'IncludeSuperusers'} and $args{'Right'} ) {
         $self->Limit(
             ALIAS           => $acl,
@@ -368,61 +370,204 @@
             ENTRYAGGREGATOR => 'OR'
         );
     }
+    return $acl;
+}
 
-    # }}}
+sub _GetEquivObjects
+{
+    my $self = shift;
+    my %args = (
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        EquivObjects           => [ ],
+        @_
+    );
+    return () unless $args{'Object'};
 
-    my ( $or_check_ticket_roles, $or_check_roles );
-    my $which_object = "$acl.ObjectType = 'RT::System'";
+    my @objects = ($args{'Object'});
+    if ( UNIVERSAL::isa( $args{'Object'}, 'RT::Ticket' ) ) {
+        # If we're looking at ticket rights, we also want to look at the associated queue rights.
+        # this is a little bit hacky, but basically, now that we've done the ticket roles magic,
+        # we load the queue object and ask all the rest of our questions about the queue.
 
-    if ( defined $args{'Object'} ) {
-        if ( ref( $args{'Object'} ) eq 'RT::Ticket' ) {
-            $or_check_ticket_roles = " OR ( $groups.Domain = 'RT::Ticket-Role' AND $groups.Instance = " . $args{'Object'}->Id . ") ";
+        # XXX: This should be abstracted into object itself
+        if( $args{'Object'}->id ) {
+            push @objects, $args{'Object'}->QueueObj;
+        } else {
+            push @objects, 'RT::Queue';
+        }
+    }
 
-# If we're looking at ticket rights, we also want to look at the associated queue rights.
-# this is a little bit hacky, but basically, now that we've done the ticket roles magic,
-# we load the queue object and ask all the rest of our questions about the queue.
-            $args{'Object'} = $args{'Object'}->QueueObj;
+    if( $args{'IncludeSystemRights'} ) {
+        push @objects, 'RT::System';
+    }
+    push @objects, @{ $args{'EquivObjects'} };
+    return grep $_, @objects;
+}
+
+sub WhoHaveRight {
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    if ( defined $args{'ObjectType'} || defined $args{'ObjectId'} ) {
+        $RT::Logger->crit( "WhoHaveRight called with the Obsolete ObjectId/ObjectType API");
+        return (undef);
+    }
+
+    my $from_role = $self->Clone;
+    $from_role->WhoHaveRoleRight( %args );
+
+    my $from_group = $self->Clone;
+    $from_group->WhoHaveGroupRight( %args );
+
+    #XXX: DIRTY HACK
+    use DBIx::SearchBuilder::Union;
+    my $union = new DBIx::SearchBuilder::Union;
+    $union->add($from_role);
+    $union->add($from_group);
+    %$self = %$union;
+    bless $self, ref($union);
+
+    return;
+}
+# }}}
+
+sub WhoHaveRoleRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    my $groups = $self->_JoinGroups( %args );
+    my $acl = $self->_JoinACL( %args );
+
+    my ($check_roles, $check_objects) = ('','');
+    
+    my @objects = $self->_GetEquivObjects( %args );
+    if ( @objects ) {
+        my @role_clauses;
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+            my $role_clause = "$groups.Domain = '$type-Role'";
+            # XXX: Groups.Instance is VARCHAR in DB, we should quote value
+            # if we want mysql 4.0 use indexes here. we MUST convert that
+            # field to integer and drop this quotes.
+            $role_clause   .= " AND $groups.Instance = '$id'" if $id;
+            push @role_clauses, "($role_clause)";
+
+            my $object_clause = "$acl.ObjectType = '$type'";
+            $object_clause   .= " AND $acl.ObjectId = $id" if $id;
+            push @object_clauses, "($object_clause)";
         }
 
-        # TODO XXX This really wants some refactoring
-        if ( ref( $args{'Object'} ) eq 'RT::Queue' ) {
-            $or_check_roles = " OR ( ( ($groups.Domain = 'RT::Queue-Role' ";
-            $or_check_roles .= "AND $groups.Instance = " . $args{'Object'}->id if ( $args{'Object'}->id );
-            $or_check_roles .= ") $or_check_ticket_roles ) " . " AND $groups.Type = $acl.PrincipalType) ";
+        $check_roles .= join ' OR ', @role_clauses;
+        $check_objects = join ' OR ', @object_clauses;
+    } else {
+        if( !$args{'IncludeSystemRights'} ) {
+            $check_objects = "($acl.ObjectType != 'RT::System')";
         }
-        if ( $args{'IncludeSystemRights'} ) {
-            $which_object .= ' OR ';
+    }
+
+    $self->_AddSubClause( "WhichObject", "($check_objects)" );
+
+    $self->_AddSubClause( "WhichRole", "($check_roles)" );
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalType',
+                  VALUE => "$groups.Type",
+                  QUOTEVALUE => 0,
+                );
+
+    # no system user
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+    return;
+}
+
+sub WhoHaveGroupRight
+{
+    my $self = shift;
+    my %args = (
+        Right                  => undef,
+        Object                 => undef,
+        IncludeSystemRights    => undef,
+        IncludeSuperusers      => undef,
+        IncludeSubgroupMembers => 1,
+        EquivObjects           => [ ],
+        @_
+    );
+
+    # Find only members of groups that have the right.
+    my $groups = $self->_JoinGroups( %args );
+
+    # Find only rows where the right granted is
+    # the one we're looking up or _possibly_ superuser
+    my $acl = $self->_JoinACL( %args );
+
+    my ($check_objects) = ('');
+    my @objects = $self->_GetEquivObjects( %args );
+
+    if ( @objects ) {
+        my @object_clauses;
+        foreach my $obj ( @objects ) {
+            my $type = ref($obj)? ref($obj): $obj;
+            my $id;
+            $id = $obj->id if ref($obj) && UNIVERSAL::can($obj, 'id') && $obj->id;
+
+            my $object_clause = "$acl.ObjectType = '$type'";
+            $object_clause   .= " AND $acl.ObjectId   = $id" if $id;
+            push @object_clauses, "($object_clause)";
         }
-        else {
-            $which_object = '';
+
+        $check_objects = join ' OR ', @object_clauses;
+    } else {
+        if( !$args{'IncludeSystemRights'} ) {
+            $check_objects = "($acl.ObjectType != 'RT::System')";
         }
-        foreach my $obj ( @{ $args{'EquivObjects'} } ) {
-            $which_object .= "($acl.ObjectType = '" . ref( $obj ) . "' AND $acl.ObjectId = " . $obj->id . ") OR ";
-        }
-        $which_object .= " ($acl.ObjectType = '" . ref( $args{'Object'} ) . "'";
-        if ( $args{'Object'}->id ) {
-            $which_object .= " AND $acl.ObjectId = " . $args{'Object'}->id;
-        }
-
-        $which_object .=  ") ";
     }
-    $self->_AddSubClause( "WhichObject", "($which_object)" );
-    $self->_AddSubClause(
-        "WhichGroup",
-            qq{ ( (    $acl.PrincipalId = $groups.id AND $acl.PrincipalType = 'Group' 
-                AND (   $groups.Domain = 'SystemInternal' OR $groups.Domain = 'UserDefined' OR $groups.Domain = 'ACLEquivalence')) 
-                $or_check_roles) }
-    );
-    # only include regular RT users
-    $self->LimitToEnabled;
+    $self->_AddSubClause( "WhichObject", "($check_objects)" );
 
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalId',
+                  VALUE => "$groups.id",
+                  QUOTEVALUE => 0,
+                );
+    $self->Limit( ALIAS => $acl,
+                  FIELD => 'PrincipalType',
+                  VALUE => 'Group',
+                );
+    
     # no system user
-    $self->Limit( ALIAS => $userprinc, FIELD => 'id', OPERATOR => '!=', VALUE => $RT::SystemUser->id);
-
+    $self->Limit( ALIAS => $self->PrincipalsAlias,
+                  FIELD => 'id',
+                  OPERATOR => '!=',
+                  VALUE => $RT::SystemUser->id
+                );
+    return;
 }
-# }}}
 
-# {{{ WhoBelongToGroups 
+# {{{ WhoBelongToGroups
 
 =head2 WhoBelongToGroups { Groups => ARRAYREF, IncludeSubgroupMembers => 1 }
 
@@ -434,31 +579,19 @@
                  IncludeSubgroupMembers => 1,
                  @_ );
 
-    # Unprivileged users can't be granted real system rights. 
+    # Unprivileged users can't be granted real system rights.
     # is this really the right thing to be saying?
     $self->LimitToPrivileged();
 
-    my $userprinc  = $self->{'princalias'};
-    my $cgm;
+    my $group_members = $self->_JoinGroupMembers( %args );
 
-    # The cachedgroupmembers table is used for unrolling group memberships to allow fast lookups 
-    # if we bind to CachedGroupMembers, we'll find all members of groups recursively.
-    # if we don't we'll find only 'direct' members of the group in question
-
-    if ( $args{'IncludeSubgroupMembers'} ) {
-        $cgm = $self->NewAlias('CachedGroupMembers');
-    }
-    else {
-        $cgm = $self->NewAlias('GroupMembers');
-    }
-
-    #Tie the users we're returning ($userprinc) to the groups that have rights granted to them ($groupprinc)
-    $self->Join( ALIAS1 => $cgm, FIELD1 => 'MemberId',
-                 ALIAS2 => $userprinc, FIELD2 => 'id' );
-
     foreach my $groupid (@{$args{'Groups'}}) {
-        $self->Limit(ALIAS => $cgm, FIELD => 'GroupId', VALUE => $groupid, QUOTEVALUE => 0, ENTRYAGGREGATOR=> 'OR')
-
+        $self->Limit( ALIAS           => $group_members,
+                      FIELD           => 'GroupId',
+                      VALUE           => $groupid,
+                      QUOTEVALUE      => 0,
+                      ENTRYAGGREGATOR => 'OR',
+                    );
     }
 }
 # }}}

-------------- next part --------------
=== ./SearchBuilder.pm
==================================================================
--- ./SearchBuilder.pm	(revision 2625)
+++ ./SearchBuilder.pm	(local)
@@ -149,7 +149,35 @@
 
     #we have no limit statements. DoSearch won't work.
     $self->_isLimited(0);
+}
 
+=head2 Clone
+
+Returns copy of the current object with all search restrictions.
+
+=cut
+
+sub Clone
+{
+    use Clone qw();
+    my $self = shift;
+
+    my $obj = bless {}, ref($self);
+    %$obj = %$self;
+
+    delete $obj->{$_} for qw(
+        items
+    );
+    $obj->{'must_redo_search'} = 1;
+    $obj->{'itemscount'}       = 0;
+    
+    $obj->{$_} = Clone::clone($obj->{$_}) for qw(
+        aliases
+        left_joins
+        subclauses
+        restrictions
+    );
+    return $obj;
 }
 
 



More information about the rt-users mailing list