#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
'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;
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' );
'Replace',
"Column $column should point to a user, but there is record #$id in table $table\n"
."where it's not true. It's ok to replace these wrong references with id of any user.\n"
- ."Note that id you enter is not checked. You can peak any user from your DB, but it's\n"
+ ."Note that id you enter is not checked. You can pick any user from your DB, but it's\n"
."may be better to create a special user for this, for example 'user_that_has_been_deleted'\n"
."or something like that.",
"$table.$column -> user #$prop{$column}"
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 equal to -c
+
+=item verbose
+
+ print additional info to STDOUT
+ it's equal to -v
+
+=item resolve
+
+ enable resolver that can delete or create some records
+
+=item force
+
+ resolve without asking questions
+
+=back
+