[Rt-commit] rt branch, 4.2/links_in_validator, created. rt-3.8.8-250-g685d8c8

Ruslan Zakirov ruz at bestpractical.com
Fri Feb 11 23:24:17 EST 2011


The branch, 4.2/links_in_validator has been created
        at  685d8c8614f752be8a127d54f426ffc54fcdc979 (commit)

- Log -----------------------------------------------------------------
commit 685d8c8614f752be8a127d54f426ffc54fcdc979
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 12 07:21:12 2011 +0300

    validate links
    
    * deal with basic errors and historical RT bugs
    * delete links to missing local object

diff --git a/sbin/rt-validator.in b/sbin/rt-validator.in
index 42b9296..90c4657 100644
--- a/sbin/rt-validator.in
+++ b/sbin/rt-validator.in
@@ -678,19 +678,8 @@ push @CHECKS, 'Transactions -> other' => sub {
         },
     );
 
-# XXX: Links need more love, uri is stored instead of id
-#    # type = DeleteLink
-#    check_integrity(
-#        'Transactions', 'OldValue' => 'Links', 'id',
-#        condition   => 's.Type = ?',
-#        bind_values => [ 'DeleteLink' ],
-#    );
-#    # type = AddLink
-#    check_integrity(
-#        'Transactions', 'NewValue' => 'Links', 'id',
-#        condition   => 's.Type = ?',
-#        bind_values => [ 'AddLink' ],
-#    );
+#   type = DeleteLink or AddLink
+#   handled in 'Links: *' checks as {New,Old}Value store URIs
 
     # type = Set, Field = Queue
     check_integrity(
@@ -889,6 +878,171 @@ push @CHECKS, 'LastUpdatedBy and Creator' => sub {
         }
     }
 };
+
+push @CHECKS, 'Links: wrong organization' => sub {
+    my @URI_USES = (
+        { model => 'Transaction', column => 'OldValue', Additional => { Type => 'DeleteLink' } },
+        { model => 'Transaction', column => 'NewValue', Additional => { Type => 'AddLink' } },
+        { model => 'Link', column => 'Target' },
+        { model => 'Link', column => 'Base' },
+    );
+
+    my $rt_uri = RT::URI::fsck_com_rt->new( $RT::SystemUser );
+    my $scheme = $rt_uri->Scheme;
+    my $prefix = $rt_uri->LocalURIPrefix;
+
+    foreach my $use ( @URI_USES ) {
+        my $table = m2t( $use->{'model'} );
+        my $column = $use->{'column'};
+
+        my $query = "SELECT id, $column FROM $table WHERE"
+            . " $column LIKE ? AND $column NOT LIKE ?";
+        my @binds = ($scheme ."://%", $prefix ."%");
+
+        while ( my ($k, $v) = each %{ $use->{'Aditional'} || {} } ) {
+            $query .= " AND $k = ?";
+            push @binds, $v;
+        }
+        my $sth = execute_query( $query, @binds );
+        while ( my ($id, $value) = $sth->fetchrow_array ) {
+            print STDERR "Record #$id in $table. Value of $column column most probably is an incorrect link\n";
+            my ($wrong_org) = ( $value =~ m{^\Q$scheme\E://(.+)/[^/]+/[0-9]*$} );
+            next unless my $replace_with = prompt(
+                'Replace',
+                "Column $column in $table is a link. Local links has scheme '$scheme'"
+                ." followed by organization name from the config file. There is record"
+                ." #$id that has scheme '$scheme', but organization is '$wrong_org'."
+                ." Most probably you changed organization, but didn't update links."
+                ." It's ok to replace these wrong links.\n",
+                "Links: wrong organization $wrong_org"
+            );
+
+            print "Updating record(s) in $table\n" if $opt{'verbose'};
+            my $wrong_prefix = $scheme . '://'. $wrong_org;
+            my $query = "UPDATE $table SET $column = ". sql_concat('?', "SUBSTR($column, ?)")
+                ." WHERE $column LIKE ?";
+            execute_query( $query, $prefix, length($wrong_prefix)+1, $wrong_prefix .'/%' );
+
+            $redo_check{'Links: wrong organization'} = 1;
+            $redo_check{'Links: LocalX for non-ticket'} = 1;
+            last; # plenty of chances we covered all cases with one update
+        }
+    }
+};
+
+push @CHECKS, 'Links: LocalX for non-ticket' => sub {
+    my $rt_uri = RT::URI::fsck_com_rt->new( $RT::SystemUser );
+    my $scheme = $rt_uri->Scheme;
+    my $prefix = $rt_uri->LocalURIPrefix;
+    my $table = m2t('Link');
+
+    foreach my $dir ( 'Target', 'Base' ) {
+        my $where = "Local$dir > 0 AND $dir LIKE ? AND $dir NOT LIKE ?";
+        my @binds = ($scheme ."://%", $prefix ."/ticket/%");
+
+        my $sth = execute_query( "SELECT id FROM $table WHERE $where", @binds );
+        while ( my ($id, $value) = $sth->fetchrow_array ) {
+            print STDERR "Record #$id in $table. Value of Local$dir is not 0\n";
+            next unless my $replace_with = prompt(
+                'Replace',
+                "Column Local$dir in $table should be 0 if $dir column is not link"
+                ." to a ticket. It's ok to replace with 0.\n",
+            );
+
+            print "Updating record(s) in $table\n" if $opt{'verbose'};
+            execute_query( "UPDATE $table SET Local$dir = 0 WHERE $where", @binds );
+            $redo_check{'Links: wrong organization'} = 1;
+
+            last; # we covered all cases with one update
+        }
+    }
+};
+
+push @CHECKS, 'Links: LocalX != X' => sub {
+    my $rt_uri = RT::URI::fsck_com_rt->new( $RT::SystemUser );
+    my $scheme = $rt_uri->Scheme;
+    my $prefix = $rt_uri->LocalURIPrefix .'/ticket/';
+    my $table = m2t('Link');
+
+    foreach my $dir ( 'Target', 'Base' ) {
+        # we limit to $dir = */ticket/* so it doesn't conflict with previouse check
+        # previouse check is more important as there was a bug in RT when Local$dir
+        # was set for not tickets
+        my $where = "Local$dir > 0 AND $dir LIKE ? AND $dir != ". sql_concat('?', "Local$dir");
+        my @binds = ($prefix ."%", $prefix);
+
+        my $sth = execute_query( "SELECT id FROM $table WHERE $where", @binds );
+        while ( my ($id, $value) = $sth->fetchrow_array ) {
+            print STDERR "Record #$id in $table. Value of $dir doesn't match ticket id in Local$dir\n";
+            next unless my $replace_with = prompt(
+                'Replace',
+                "For ticket links column $dir in $table table should end with"
+                ." ticket id from Local$dir. It's probably ok to fix $dir column.\n",
+            );
+
+            print "Updating record(s) in $table\n" if $opt{'verbose'};
+            execute_query(
+                "UPDATE $table SET $dir = ". sql_concat('?', "Local$dir") ." WHERE $where",
+                $prefix, @binds
+            );
+
+            last; # we covered all cases with one update
+        }
+    }
+};
+
+push @CHECKS, 'Links: missing object' => sub {
+    my @URI_USES = (
+        { model => 'Transaction', column => 'OldValue', Additional => { Type => 'DeleteLink' } },
+        { model => 'Transaction', column => 'NewValue', Additional => { Type => 'AddLink' } },
+        { model => 'Link', column => 'Target' },
+        { model => 'Link', column => 'Base' },
+    );
+
+    my $rt_uri = RT::URI::fsck_com_rt->new( $RT::SystemUser );
+    my $scheme = $rt_uri->Scheme;
+    my $prefix = $rt_uri->LocalURIPrefix;
+
+    foreach my $use ( @URI_USES ) {
+        my $stable = m2t( $use->{'model'} );
+        my $scolumn = $use->{'column'};
+
+        foreach my $tmodel ( @models ) {
+            my $tclass = 'RT::'. $tmodel;
+            my $ttable = m2t($tmodel);
+
+            my $tprefix = $prefix .'/'. ($tclass eq 'RT::Ticket'? 'ticket' : $tclass) .'/';
+
+            my $query = "SELECT s.id FROM $stable s LEFT JOIN $ttable t "
+                ." ON t.id = ". sql_str2int("SUBSTR(s.$scolumn, ?)")
+                ." WHERE s.$scolumn LIKE ? AND t.id IS NULL";
+            my @binds = (length($tprefix) + 1, $tprefix.'%');
+
+            while ( my ($k, $v) = each %{ $use->{'Aditional'} || {} } ) {
+                $query .= " AND s.$k = ?";
+                push @binds, $v;
+            }
+
+            my $sth = execute_query( $query, @binds );
+            while ( my ($sid) = $sth->fetchrow_array ) {
+                print STDERR "Link in $scolumn column in record #$sid in $stable table points"
+                    ." to not existing object.\n";
+                next unless prompt(
+                    'Delete',
+                    "Column $scolumn in $stable table is a link to an object that doesn't exist."
+                    ." You can delete such records, however make sure there is no other"
+                    ." errors with links.\n",
+                    'Link to a missing object in $ttable'
+                );
+
+                delete_record($stable, $sid);
+            }
+        }
+    }
+};
+
+
+
 my %CHECKS = @CHECKS;
 
 @do_check = do { my $i = 1; grep $i++%2, @CHECKS };
@@ -945,7 +1099,8 @@ sub check_integrity {
             print STDERR "\t$scols[$i] => '$set[$i]' => $tcols[$i]\n";
         }
         print STDERR "\t". describe( $stable, $sid ) ."\n";
-        $args{'action'}->( $sid, map { $scols[$_] => $set[$_] } (0 .. (@scols-1)) ) if $args{'action'};
+        $args{'action'}->( $sid, map { $scols[$_] => $set[$_] } (0 .. (@scols-1)) )
+            if $args{'action'};
     }
 }
 
@@ -1059,6 +1214,24 @@ sub execute_query {
     return $sth;
 }
 
+sub sql_concat {
+    return $_[0] if @_ <= 1;
+
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq 'Pg' ) {
+        return '('. join( ' || ', @_ ) .')';
+    }
+    return sql_concat('CONCAT('. join( ', ', splice @_, 0, 2 ).')', @_);
+}
+
+sub sql_str2int {
+    my $db_type = RT->Config->Get('DatabaseType');
+    if ( $db_type eq 'Pg' ) {
+        return "($_[0])::integer";
+    }
+    return $_[0];
+}
+
 { my %cached_answer;
 sub prompt {
     my $action = shift;

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


More information about the Rt-commit mailing list