1
0

checkDBschema.pl 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/perl
  2. #
  3. # Copyright (C) Roman Dmitriev, rnd@rajven.ru
  4. #
  5. use utf8;
  6. use open ":encoding(utf8)";
  7. use open ':std', ':encoding(UTF-8)';
  8. use Encode;
  9. no warnings 'utf8';
  10. use English;
  11. use FindBin '$Bin';
  12. use lib "/opt/Eye/scripts";
  13. use Getopt::Long qw(GetOptions);
  14. use Data::Dumper;
  15. use eyelib::config;
  16. use eyelib::main;
  17. use eyelib::database;
  18. use eyelib::common;
  19. use eyelib::net_utils;
  20. use strict;
  21. use warnings;
  22. # === Получаем имя чистой БД ===
  23. my $clear_db_name = $DBNAME . "_test";
  24. # === Подключаемся к рабочей БД ===
  25. my $work_db = init_db();
  26. # === Подключаемся к чистой БД ===
  27. my $clear_db;
  28. if ($config_ref{DBTYPE} eq 'mysql') {
  29. my $dsn = "dbi:mysql:database=$clear_db_name;host=$DBHOST;port=3306;mysql_local_infile=1";
  30. $clear_db = DBI->connect($dsn, $DBUSER, $DBPASS, {
  31. RaiseError => 0,
  32. AutoCommit => 1,
  33. mysql_enable_utf8 => 1
  34. });
  35. if (!defined $clear_db) {
  36. die "Cannot connect to MySQL database '$clear_db_name': $DBI::errstr\n";
  37. }
  38. $clear_db->do('SET NAMES utf8mb4');
  39. } else {
  40. my $dsn = "dbi:Pg:dbname=$clear_db_name;host=$DBHOST;port=5432;";
  41. $clear_db = DBI->connect($dsn, $DBUSER, $DBPASS, {
  42. RaiseError => 0,
  43. AutoCommit => 1,
  44. pg_enable_utf8 => 1,
  45. pg_server_prepare => 0
  46. });
  47. if (!defined $clear_db) {
  48. die "Cannot connect to PostgreSQL database '$clear_db_name': $DBI::errstr\n";
  49. }
  50. }
  51. # === Функция нормализации значения по умолчанию ===
  52. sub normalize_default {
  53. my ($default, $db_type) = @_;
  54. return undef unless defined $default;
  55. if ($db_type eq 'mysql') {
  56. # Убираем кавычки, если строка
  57. $default =~ s/^'(.*)'$/$1/;
  58. # NULL → undef
  59. return undef if lc($default) eq 'null';
  60. } else {
  61. # PostgreSQL: уже в нормальном виде
  62. return undef if lc($default) eq 'null';
  63. }
  64. return $default;
  65. }
  66. # === Сбор схемы для БД ===
  67. sub get_schema {
  68. my ($db, $db_type, $db_name) = @_;
  69. my %schema;
  70. my @tables;
  71. if ($db_type eq 'mysql') {
  72. my @rows = get_records_sql($db, 'SHOW TABLES');
  73. my $idx = 'Tables_in_' . $db_name;
  74. @tables = map { $_->{$idx} } grep { $_ && exists $_->{$idx} } @rows;
  75. } else {
  76. my @rows = get_records_sql($db, "SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
  77. @tables = map { $_->{tablename} } @rows;
  78. }
  79. for my $table (@tables) {
  80. my %cols = get_table_columns($db, $table);
  81. # get_table_columns уже возвращает lowercase имена
  82. for my $col (keys %cols) {
  83. my $info = $cols{$col};
  84. $schema{$table}{$col} = {
  85. type => $info->{type} // '',
  86. nullable => $info->{nullable} // 1,
  87. default => normalize_default($info->{default}, $db_type),
  88. };
  89. }
  90. }
  91. return %schema;
  92. }
  93. # === Сбор схем ===
  94. print "Fetching schema from working database '$DBNAME'...\n";
  95. my %work_schema = get_schema($work_db, $config_ref{DBTYPE}, $DBNAME);
  96. print "Fetching schema from clean database '$clear_db_name'...\n";
  97. my %clear_schema = get_schema($clear_db, $config_ref{DBTYPE}, $clear_db_name);
  98. print "\n=== Comparing DB schemas ===\n\n";
  99. my $has_critical_error = 0;
  100. # === 1. Проверка: всё ли из чистой БД есть в рабочей? ===
  101. for my $table (keys %clear_schema) {
  102. if (!exists $work_schema{$table}) {
  103. print "❗ ERROR: Table '$table' exists in clean DB but not in working DB!\n";
  104. $has_critical_error = 1;
  105. next;
  106. }
  107. for my $col (keys %{ $clear_schema{$table} }) {
  108. if (!exists $work_schema{$table}{$col}) {
  109. print "❗ ERROR: Column '$col' in table '$table' exists in clean DB but not in working DB!\n";
  110. $has_critical_error = 1;
  111. next;
  112. }
  113. # === Сравнение типов ===
  114. my $clean_type = $clear_schema{$table}{$col}{type} // '';
  115. my $work_type = $work_schema{$table}{$col}{type} // '';
  116. # Нормализуем типы для сравнения (MySQL vs PG)
  117. # if ($config_ref{DBTYPE} eq 'mysql') {
  118. # Пример: TINYINT(1) → boolean-like, но у нас SMALLINT
  119. # Для простоты сравниваем как строки
  120. # }
  121. if ($clean_type ne $work_type) {
  122. print "❗ ERROR: Column '$col' in table '$table' has different type:\n";
  123. print " Clean: '$clean_type', Working: '$work_type'\n";
  124. $has_critical_error = 1;
  125. }
  126. # === Сравнение NULL ===
  127. my $clean_null = $clear_schema{$table}{$col}{nullable} // 1;
  128. my $work_null = $work_schema{$table}{$col}{nullable} // 1;
  129. if ($clean_null != $work_null) {
  130. my $clean_str = $clean_null ? "NULL" : "NOT NULL";
  131. my $work_str = $work_null ? "NULL" : "NOT NULL";
  132. print "❗ ERROR: Column '$col' in table '$table' has different NULL setting:\n";
  133. print " Clean: $clean_str, Working: $work_str\n";
  134. $has_critical_error = 1;
  135. }
  136. # === Сравнение DEFAULT ===
  137. my $clean_def = $clear_schema{$table}{$col}{default};
  138. my $work_def = $work_schema{$table}{$col}{default};
  139. if (!defined $clean_def && !defined $work_def) {
  140. # ok
  141. } elsif (!defined $clean_def || !defined $work_def) {
  142. print "❗ ERROR: Column '$col' in table '$table' has different DEFAULT (one is NULL):\n";
  143. print " Clean: ", defined $clean_def ? "'$clean_def'" : "NULL", "\n";
  144. print " Working: ", defined $work_def ? "'$work_def'" : "NULL", "\n";
  145. $has_critical_error = 1;
  146. } elsif ($clean_def ne $work_def) {
  147. print "❗ ERROR: Column '$col' in table '$table' has different DEFAULT:\n";
  148. print " Clean: '$clean_def', Working: '$work_def'\n";
  149. $has_critical_error = 1;
  150. }
  151. }
  152. }
  153. # === 2. Проверка: есть ли лишнее в рабочей БД? ===
  154. for my $table (keys %work_schema) {
  155. if (!exists $clear_schema{$table}) {
  156. print "⚠ WARNING: Table '$table' exists in working DB but not in clean DB — will be skipped.\n";
  157. next;
  158. }
  159. for my $col (keys %{ $work_schema{$table} }) {
  160. if (!exists $clear_schema{$table}{$col}) {
  161. print "⚠ WARNING: Column '$col' in table '$table' exists in working DB but not in clean DB — will be ignored.\n";
  162. }
  163. }
  164. }
  165. if ($has_critical_error) {
  166. print "\nSchema validation failed: structural differences found.\n";
  167. exit 103;
  168. }
  169. print "✅ Schema validation passed.\n\n";
  170. exit 0;