File: Check.pm

package info (click to toggle)
otrs2 6.0.32-6
  • links: PTS
  • area: non-free
  • in suites: bullseye
  • size: 197,336 kB
  • sloc: perl: 1,003,018; javascript: 75,060; xml: 70,883; php: 51,819; sql: 22,361; sh: 379; makefile: 51
file content (379 lines) | stat: -rw-r--r-- 10,818 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# --
# Copyright (C) 2001-2021 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;