#!@PERL@
# BEGIN BPS TAGGED BLOCK {{{
-#
+#
# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
-# <jesse@bestpractical.com>
-#
+#
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
+# <sales@bestpractical.com>
+#
# (Except where explicitly superseded by other copyright notices)
-#
-#
+#
+#
# LICENSE:
-#
+#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# been provided with this software, but in any event can be snarfed
# from www.gnu.org.
-#
+#
# This work is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 or visit their web page on the internet at
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
+#
+#
# CONTRIBUTION SUBMISSION POLICY:
-#
+#
# (The following paragraph is not intended to limit the rights granted
# to you to modify and distribute this software under the terms of
# the GNU General Public License and is only of importance to you if
# you choose to contribute your changes and enhancements to the
# community by submitting them to Best Practical Solutions, LLC.)
-#
+#
# By intentionally submitting any modifications, corrections or
# derivatives to this work, or any other work intended for use with
# Request Tracker, to Best Practical Solutions, LLC, you confirm that
# royalty-free, perpetual, license to use, copy, create derivative
# works based on those contributions, and sublicense and distribute
# those contributions and any derivatives thereof.
-#
+#
# END BPS TAGGED BLOCK }}}
use strict;
use warnings;
'resolve',
'force',
'verbose|v',
+ 'help|h',
);
-usage() unless $opt{'check'};
-usage_warning() if $opt{'resolve'} && !$opt{'force'};
-
-sub usage {
- print STDERR <<END;
-Usage: $0 options
-
-Options:
-
- $0 --check
- $0 --check --verbose
- $0 --check --verbose --resolve
- $0 --check --verbose --resolve --force
-
---check - is mandatory argument, you can use -c, as well.
---verbose - print additional info to STDOUT
---resolve - enable resolver that can delete or create some records
---force - resolve without asking questions
-
-Description:
+if ( $opt{help} || !$opt{check} ) {
+ require Pod::Usage;
+ print Pod::Usage::pod2usage( { verbose => 2 } );
+ exit;
+}
-This script checks integrity of records in RT's DB. May delete some invalid
-records or ressurect accidentally deleted.
+usage_warning() if $opt{'resolve'} && !$opt{'force'};
-END
- exit 1;
-}
sub usage_warning {
print <<END;
Press enter to continue.
END
- <>;
+# Read a line of text, any line of text
+ <STDIN>;
}
use RT;
GroupMembers => [ 'CGM vs. GM' ],
CachedGroupMembers => [ 'CGM vs. GM' ],
};
+$redo_on{'Update'} = {
+ Groups => ['User Defined Group Name uniqueness'],
+};
my %describe_cb;
%describe_cb = (
my $model = shift;
return $cache{$model} if $cache{$model};
my $class = "RT::$model";
- my $object = $class->new( $RT::SystemUser );
+ my $object = $class->new( RT->SystemUser );
return $cache{$model} = $object->Table;
} }
my @CHECKS;
foreach my $table ( qw(Users Groups) ) {
push @CHECKS, "$table -> Principals" => sub {
- my $msg = "A record in $table refers not existing record in Principals."
- ." The script can either create missing record in Principals"
- ." or delete record in $table.";
+ my $msg = "A record in $table refers to a nonexistent record in Principals."
+ ." The script can either create the missing record in Principals"
+ ." or delete the record in $table.";
my ($type) = ($table =~ /^(.*)s$/);
check_integrity(
$table, 'id' => 'Principals', 'id',
};
push @CHECKS, "Principals -> $table" => sub {
- my $msg = "A record in Principals refers not existing record in $table."
- ." In some cases it's possible to resurrect manually such records,"
- ." but this utility can only delete";
+ my $msg = "A record in Principals refers to a nonexistent record in $table."
+ ." In some cases it's possible to manually resurrect such records,"
+ ." but this utility can only delete records.";
check_integrity(
'Principals', 'id' => $table, 'id',
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found role group of not existant queue."
+ 'Delete', "Found a role group of a nonexistent queue."
);
delete_record( 'Groups', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a role group of not existant ticket."
+ 'Delete', "Found a role group of a nonexistent ticket."
);
delete_record( 'Groups', $id );
);
};
+push @CHECKS, 'System internal group uniqueness' => sub {
+ check_uniqueness(
+ 'Groups',
+ columns => ['Instance', 'Type'],
+ condition => '.Domain = ?',
+ bind_values => [ 'SystemInternal' ],
+ );
+};
+
+# CHECK that user defined group names are unique
+push @CHECKS, 'User Defined Group Name uniqueness' => sub {
+ check_uniqueness(
+ 'Groups',
+ columns => ['Name'],
+ condition => '.Domain = ?',
+ bind_values => [ 'UserDefined' ],
+ extra_tables => ['Principals sp', 'Principals tp'],
+ extra_condition => join(" and ", map { "$_.id = ${_}p.ObjectId and ${_}p.PrincipalType = ? and ${_}p.Disabled != 1" } qw(s t)),
+ extra_values => ['Group', 'Group'],
+ action => sub {
+ return unless prompt(
+ 'Rename', "Found a user defined group with a non-unique Name."
+ );
+
+ my $id = shift;
+ my %cols = @_;
+ update_records('Groups', { id => $id }, { Name => join('-', $cols{'Name'}, $id) });
+ },
+ );
+};
push @CHECKS, 'GMs -> Groups, Members' => sub {
my $msg = "A record in GroupMembers references an object that doesn't exist."
- ." May be you deleted a group or principal directly from DB?"
- ." Usually it's ok to delete such records.";
+ ." Maybe you deleted a group or principal directly from the database?"
+ ." Usually it's OK to delete such records.";
check_integrity(
'GroupMembers', 'GroupId' => 'Groups', 'id',
action => sub {
"Found a record in GroupMembers that has no direct duplicate in CachedGroupMembers table."
);
- my $gm = RT::GroupMember->new( $RT::SystemUser );
+ my $gm = RT::GroupMember->new( RT->SystemUser );
$gm->Load( $id );
die "Couldn't load GM record #$id" unless $gm->id;
my $cgm = create_record( 'CachedGroupMembers',
return unless prompt(
'Delete',
"Found a record in CachedGroupMembers for a (Group, Member) pair"
- ." that doesn't exist in GroupMembers table."
+ ." that doesn't exist in the GroupMembers table."
);
delete_record( 'CachedGroupMembers', $id );
." duplicate in CachedGroupMembers table."
);
- my $g = RT::Group->new( $RT::SystemUser );
+ my $g = RT::Group->new( RT->SystemUser );
$g->Load( $id );
die "Couldn't load group #$id" unless $g->id;
die "Loaded group by $id has id ". $g->id unless $g->id == $id;
my $id = shift;
return unless prompt(
'Delete',
- "Found a record in CachedGroupMembers with Via referencing not existing record."
+ "Found a record in CachedGroupMembers with Via that references a nonexistent record."
);
delete_record( 'CachedGroupMembers', $id );
my $id = shift;
return unless prompt(
'Delete',
- "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table."
+ "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table."
);
delete_record( 'CachedGroupMembers', $id );
my $id = shift;
return unless prompt(
'Delete',
- "Found a record in CachedGroupMembers that referencing not existant record in CachedGroupMembers table."
+ "Found a record in CachedGroupMembers that references a nonexistent record in CachedGroupMembers table."
);
delete_record( 'CachedGroupMembers', $id );
my $id = shift;
return unless prompt(
'Delete',
- "Found a ticket that's been merged into a ticket that don't exist anymore."
+ "Found a ticket that's been merged into a ticket that no longer exists."
);
delete_record( 'Tickets', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction regarding changes of Owner,"
- ." but User with id stored in OldValue column doesn't exist anymore."
+ 'Delete', "Found a transaction regarding Owner changes,"
+ ." but the User with id stored in OldValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction regarding changes of Owner,"
- ." but User with id stored in NewValue column doesn't exist anymore."
+ 'Delete', "Found a transaction regarding Owner changes,"
+ ." but the User with id stored in NewValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction describing watchers change,"
- ." but User with id stored in OldValue column doesn't exist anymore."
+ 'Delete', "Found a transaction describing watcher changes,"
+ ." but the User with id stored in OldValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction describing watchers change,"
- ." but User with id stored in NewValue column doesn't exist anymore."
+ 'Delete', "Found a transaction describing watcher changes,"
+ ." but the User with id stored in NewValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction describing queue change,"
- ." but Queue with id stored in NewValue column doesn't exist anymore."
+ 'Delete', "Found a transaction describing a queue change,"
+ ." but the Queue with id stored in the NewValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
action => sub {
my $id = shift;
return unless prompt(
- 'Delete', "Found a transaction describing queue change,"
- ." but Queue with id stored in OldValue column doesn't exist anymore."
+ 'Delete', "Found a transaction describing a queue change,"
+ ." but the Queue with id stored in the OldValue column doesn't exist anymore."
);
delete_record( 'Transactions', $id );
my %fix = ();
foreach my $model ( @models ) {
my $class = "RT::$model";
- my $object = $class->new( $RT::SystemUser );
+ my $object = $class->new( RT->SystemUser );
foreach my $column ( qw(LastUpdatedBy Creator) ) {
next unless $object->_Accessible( $column, 'auto' );
push @CHECKS, 'LastUpdatedBy and Creator' => sub {
foreach my $model ( @models ) {
my $class = "RT::$model";
- my $object = $class->new( $RT::SystemUser );
+ my $object = $class->new( RT->SystemUser );
my $table = $object->Table;
foreach my $column ( qw(LastUpdatedBy Creator) ) {
next unless $object->_Accessible( $column, 'auto' );
my $sth = execute_query( $query, @binds );
while ( my ($sid, @set) = $sth->fetchrow_array ) {
- print STDERR "Record #$sid in $stable references not existent record in $ttable\n";
+ print STDERR "Record #$sid in $stable references a nonexistent record in $ttable\n";
for ( my $i = 0; $i < @scols; $i++ ) {
print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n";
}
my @columns = @{ $args{'columns'} };
print "Checking uniqueness of ( ", join(', ', map "'$_'", @columns )," ) in table '$on'\n"
- if $opt{'versbose'};
+ if $opt{'verbose'};
my ($scond, $tcond);
if ( $scond = $tcond = $args{'condition'} ) {
." FROM $on s LEFT JOIN $on t "
." ON s.id != t.id AND ". join(' AND ', map "s.$_ = t.$_", @columns)
. ($tcond? " AND ( $tcond )": "")
+ . ($args{'extra_tables'} ? join(", ", "", @{$args{'extra_tables'}}) : "")
." WHERE t.id IS NOT NULL "
." AND ". join(' AND ', map "s.$_ IS NOT NULL", @columns);
$query .= " AND ( $scond )" if $scond;
+ $query .= " AND ( $args{'extra_condition'} )" if $args{'extra_condition'};
my $sth = execute_query(
$query,
- $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): ()
+ $args{'bind_values'}? (@{ $args{'bind_values'} }, @{ $args{'bind_values'} }): (),
+ $args{'extra_values'}? (@{ $args{'extra_values'} }): ()
);
while ( my ($sid, $tid, @set) = $sth->fetchrow_array ) {
print STDERR "Record #$tid in $on has the same set of values as $sid\n";
for ( my $i = 0; $i < @columns; $i++ ) {
print STDERR "\t$columns[$i] => '$set[$i]'\n";
}
+ $args{'action'}->( $tid, map { $columns[$_] => $set[$_] } (0 .. (@columns-1)) ) if $args{'action'};
}
}
} }
1;
+
+__END__
+
+=head1 NAME
+
+rt-validator - check and correct validity of records in RT's database
+
+=head1 SYNOPSIS
+
+ rt-validator --check
+ rt-validator --check --verbose
+ rt-validator --check --verbose --resolve
+ rt-validator --check --verbose --resolve --force
+
+=head1 DESCRIPTION
+
+This script checks integrity of records in RT's DB. May delete some invalid
+records or ressurect accidentally deleted.
+
+=head1 OPTIONS
+
+=over
+
+=item check
+
+ mandatory.
+
+ it's equall to -c
+
+=item verbose
+
+ print additional info to STDOUT
+ it's equall to -v
+
+=item resolve
+
+ enable resolver that can delete or create some records
+
+=item force
+
+ resolve without asking questions
+
+=back
+