init III
This commit is contained in:
379
Perl OTRS/Kernel/System/Console/Command/Maint/Database/Check.pm
Normal file
379
Perl OTRS/Kernel/System/Console/Command/Maint/Database/Check.pm
Normal file
@@ -0,0 +1,379 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::System::Console::Command::Maint::Database::Check;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Cache',
|
||||
'Kernel::System::DB',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Check OTRS database connectivity.');
|
||||
$Self->AddOption(
|
||||
Name => 'repair',
|
||||
Description => 'Repairs invalid database schema (like deleting invalid default values for datetime fields).',
|
||||
Required => 0,
|
||||
HasValue => 0,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Param{Options}->{Repair} = $Self->GetOption('repair');
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
# print database information
|
||||
my $DatabaseDSN = $DBObject->{DSN};
|
||||
my $DatabaseUser = $DBObject->{USER};
|
||||
|
||||
$Self->Print("<yellow>Trying to connect to database '$DatabaseDSN' with user '$DatabaseUser'...</yellow>\n");
|
||||
|
||||
# Check for database state error.
|
||||
if ( !$DBObject ) {
|
||||
$Self->PrintError('Connection failed.');
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
# Try to get some data from the database.
|
||||
$DBObject->Prepare( SQL => "SELECT * FROM valid" );
|
||||
my $Check = 0;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
$Check++;
|
||||
}
|
||||
if ( !$Check ) {
|
||||
$Self->PrintError("Connection was successful, but database content is missing.");
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
$Self->Print("<green>Connection successful.</green>\n");
|
||||
|
||||
if ( $DBObject->{'DB::Type'} eq 'mysql' ) {
|
||||
$Self->_CheckMySQLDefaultStorageEngine(%Param);
|
||||
$Self->_CheckMySQLInvalidDefaultValues(%Param);
|
||||
}
|
||||
elsif ( $DBObject->{'DB::Type'} eq 'postgresql' ) {
|
||||
$Self->_CheckPostgresPrimaryKeySequences(%Param);
|
||||
}
|
||||
elsif ( $DBObject->{'DB::Type'} eq 'oracle' ) {
|
||||
$Self->_CheckOraclePrimaryKeySequencesAndTrigers(%Param);
|
||||
}
|
||||
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
sub _CheckMySQLDefaultStorageEngine {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
# Only for MySQL.
|
||||
return 1 if $DBObject->{'DB::Type'} ne 'mysql';
|
||||
|
||||
# Check for common MySQL issue where default storage engine is different
|
||||
# from initial OTRS table; this can happen when MySQL is upgraded from
|
||||
# 5.1 > 5.5.
|
||||
# Default storage engine variable has changed its name in MySQL 5.5.3, we need to support both of them for now.
|
||||
# <= 5.5.2 storage_engine
|
||||
# >= 5.5.3 default_storage_engine
|
||||
my $StorageEngine;
|
||||
$DBObject->Prepare(
|
||||
SQL => "SHOW VARIABLES WHERE variable_name = 'storage_engine'",
|
||||
);
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
$StorageEngine = $Row[1];
|
||||
}
|
||||
|
||||
if ( !$StorageEngine ) {
|
||||
$DBObject->Prepare(
|
||||
SQL => "SHOW VARIABLES WHERE variable_name = 'default_storage_engine'",
|
||||
);
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
$StorageEngine = $Row[1];
|
||||
}
|
||||
}
|
||||
|
||||
$DBObject->Prepare(
|
||||
SQL => "SHOW TABLE STATUS WHERE engine != ?",
|
||||
Bind => [ \$StorageEngine ],
|
||||
);
|
||||
my @Tables;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @Tables, $Row[0];
|
||||
}
|
||||
if (@Tables) {
|
||||
my $Error = "Your storage engine is $StorageEngine.\n";
|
||||
$Error .= "These tables use a different storage engine:\n\n";
|
||||
$Error .= join( "\n", sort @Tables );
|
||||
$Error .= "\n\n *** Please correct these problems! *** \n\n";
|
||||
|
||||
$Self->PrintError($Error);
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _CheckMySQLInvalidDefaultValues {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
# Only for MySQL.
|
||||
return 1 if $DBObject->{'DB::Type'} ne 'mysql';
|
||||
|
||||
my $DatabaseName = $Kernel::OM->Get('Kernel::Config')->Get('Database');
|
||||
|
||||
# Check for datetime fields with invalid default values
|
||||
# (default values with a date of "0000-00-00 00:00:00").
|
||||
$DBObject->Prepare(
|
||||
SQL => '
|
||||
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = ?
|
||||
AND DATA_TYPE = "datetime"
|
||||
AND COLUMN_DEFAULT = "0000-00-00 00:00:00"
|
||||
',
|
||||
Bind => [ \$DatabaseName ],
|
||||
);
|
||||
|
||||
# Collect all tables, their columns and default values like described above.
|
||||
my %TablesWithWrongDefaultColumns;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
my $Table = $Row[0];
|
||||
my $Column = $Row[1];
|
||||
my $Default = $Row[2];
|
||||
$TablesWithWrongDefaultColumns{$Table}->{$Column} = $Default;
|
||||
}
|
||||
|
||||
# Everything was ok.
|
||||
return 1 if !%TablesWithWrongDefaultColumns;
|
||||
|
||||
# Only if something wrong was found.
|
||||
my $Error = "Your database contains tables with some wrong default values.";
|
||||
|
||||
# Show different error message depending on repair mode.
|
||||
if ( $Param{Options}->{Repair} ) {
|
||||
$Error .= "\nRepairing now ...\n";
|
||||
}
|
||||
else {
|
||||
$Error
|
||||
.= "\n\n *** Please correct these problems manually with the following SQL statements or use 'otrs.Console.pl $Self->{Name} --repair'. *** \n\n";
|
||||
}
|
||||
|
||||
my @SQLRepairStatements;
|
||||
|
||||
TABLE:
|
||||
for my $Table ( sort keys %TablesWithWrongDefaultColumns ) {
|
||||
|
||||
my @AlterColumnsSQL;
|
||||
|
||||
for my $Column ( sort keys %{ $TablesWithWrongDefaultColumns{$Table} } ) {
|
||||
|
||||
my $Default = $TablesWithWrongDefaultColumns{$Table}->{$Column};
|
||||
|
||||
push @AlterColumnsSQL, "ALTER COLUMN $Column DROP DEFAULT";
|
||||
}
|
||||
|
||||
next TABLE if !@AlterColumnsSQL;
|
||||
|
||||
my $SQL = "ALTER TABLE $Table ";
|
||||
$SQL .= join ', ', @AlterColumnsSQL;
|
||||
push @SQLRepairStatements, $SQL;
|
||||
}
|
||||
|
||||
# Show the SQL statements to the user for manually repair.
|
||||
if ( !$Param{Options}->{Repair} ) {
|
||||
$Error .= join ";\n", @SQLRepairStatements;
|
||||
$Error .= ";\n\n";
|
||||
}
|
||||
|
||||
if ( $Param{Options}->{Repair} ) {
|
||||
$Error = "<yellow>$Error</yellow>\n";
|
||||
$Self->Print($Error);
|
||||
}
|
||||
else {
|
||||
$Self->PrintError($Error);
|
||||
}
|
||||
|
||||
# Repair the default values.
|
||||
if ( $Param{Options}->{Repair} ) {
|
||||
for my $SQL (@SQLRepairStatements) {
|
||||
return if !$DBObject->Do( SQL => $SQL );
|
||||
}
|
||||
|
||||
# Clean up the support data collector cache.
|
||||
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
|
||||
Type => 'SupportDataCollector',
|
||||
);
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _CheckPostgresPrimaryKeySequences {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
# Only for Postgres.
|
||||
return 1 if $DBObject->{'DB::Type'} ne 'postgresql';
|
||||
|
||||
# Get all table names
|
||||
my @Tables = $DBObject->ListTables();
|
||||
|
||||
my %SequenceNameFromTableName;
|
||||
for my $TableName (@Tables) {
|
||||
|
||||
my $Sequence = $DBObject->{Backend}->_SequenceName(
|
||||
TableName => $TableName,
|
||||
);
|
||||
|
||||
# Special handling for a table with no id column but with a object_id column.
|
||||
if ( $TableName eq 'dynamic_field_obj_id_name' ) {
|
||||
$Sequence = 'dynamic_field_obj_id_name_object_id_seq';
|
||||
}
|
||||
|
||||
# Convert to lower case.
|
||||
$Sequence = lc $Sequence;
|
||||
|
||||
$SequenceNameFromTableName{$Sequence} = 1;
|
||||
}
|
||||
|
||||
# Get all sequence names.
|
||||
$DBObject->Prepare(
|
||||
SQL => "SELECT relname FROM pg_class WHERE relkind = 'S'",
|
||||
);
|
||||
|
||||
my @SequenceNames;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @SequenceNames, lc $Row[0];
|
||||
}
|
||||
|
||||
my @WrongSequenceNames;
|
||||
SEQUENCE:
|
||||
for my $SequenceName (@SequenceNames) {
|
||||
|
||||
next SEQUENCE if $SequenceNameFromTableName{$SequenceName};
|
||||
|
||||
# Remember wrong sequence name.
|
||||
push @WrongSequenceNames, $SequenceName;
|
||||
}
|
||||
|
||||
# Show error message.
|
||||
if (@WrongSequenceNames) {
|
||||
my $Error = "The following sequences with possible wrong names have been found. Please rename them manually.\n"
|
||||
. join "\n", @WrongSequenceNames;
|
||||
$Self->PrintError($Error);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _CheckOraclePrimaryKeySequencesAndTrigers {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
# Only for Oracle.
|
||||
return 1 if $DBObject->{'DB::Type'} ne 'oracle';
|
||||
|
||||
# Get all table names.
|
||||
my @Tables = $DBObject->ListTables();
|
||||
|
||||
my %SequenceNameFromTableName;
|
||||
for my $TableName (@Tables) {
|
||||
|
||||
my $Sequence = $DBObject->{Backend}->_SequenceName(
|
||||
TableName => $TableName,
|
||||
);
|
||||
|
||||
# Convert to lower case.
|
||||
$Sequence = lc $Sequence;
|
||||
|
||||
$SequenceNameFromTableName{$Sequence} = 1;
|
||||
}
|
||||
|
||||
# Get all sequence names.
|
||||
$DBObject->Prepare(
|
||||
SQL => 'SELECT sequence_name FROM user_sequences',
|
||||
);
|
||||
|
||||
my @SequenceNames;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
|
||||
# Convert to lower case.
|
||||
push @SequenceNames, lc $Row[0];
|
||||
}
|
||||
|
||||
my @WrongSequenceNames;
|
||||
SEQUENCE:
|
||||
for my $SequenceName (@SequenceNames) {
|
||||
|
||||
next SEQUENCE if $SequenceNameFromTableName{$SequenceName};
|
||||
|
||||
# Remember wrong sequence name.
|
||||
push @WrongSequenceNames, $SequenceName;
|
||||
}
|
||||
|
||||
# Get all trigger names.
|
||||
$DBObject->Prepare(
|
||||
SQL => 'SELECT trigger_name FROM user_triggers',
|
||||
);
|
||||
|
||||
my @TriggerNames;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
|
||||
# Convert to lower case.
|
||||
push @TriggerNames, lc $Row[0];
|
||||
}
|
||||
|
||||
my @WrongTriggerNames;
|
||||
TRIGGER:
|
||||
for my $TriggerName (@TriggerNames) {
|
||||
|
||||
my $SequenceName = $TriggerName;
|
||||
|
||||
# Remove the last part of the sequence name.
|
||||
$SequenceName =~ s{ _t \z }{}xms;
|
||||
|
||||
next TRIGGER if $SequenceNameFromTableName{$SequenceName};
|
||||
|
||||
# Remember wrong trigger name.
|
||||
push @WrongTriggerNames, $TriggerName;
|
||||
}
|
||||
|
||||
# Show error messages.
|
||||
my $Error;
|
||||
if (@WrongSequenceNames) {
|
||||
$Error = "The following sequences with possible wrong names have been found. Please rename them manually.\n"
|
||||
. join "\n", @WrongSequenceNames;
|
||||
$Self->PrintError($Error);
|
||||
}
|
||||
if (@WrongTriggerNames) {
|
||||
$Error = "The following triggers with possible wrong names have been found. Please rename them manually.\n"
|
||||
. join "\n", @WrongTriggerNames;
|
||||
$Self->PrintError($Error);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user