Dmitriev Roman před 3 měsíci
rodič
revize
7b4aca14e7
6 změnil soubory, kde provedl 426 přidání a 313 odebrání
  1. 15 15
      html/inc/sql.php
  2. 8 0
      install-eye.sh
  3. 42 18
      scripts/eye-statd.pl
  4. 1 1
      scripts/eyelib/common.pm
  5. 359 278
      scripts/eyelib/database.pm
  6. 1 1
      scripts/eyelib/main.pm

+ 15 - 15
html/inc/sql.php

@@ -633,7 +633,7 @@ function get_id_record($db, $table, $filter, $params=[]) {
 function set_changed($db, $id)
 {
     $auth['changed'] = 1;
-    update_record($db, "user_auth", "id=" . $id, $auth);
+    update_record($db, "user_auth", "id=?", $auth, [$id]);
 }
 
 function allow_update($table, $action = 'update', $field = '')
@@ -1234,7 +1234,7 @@ function delete_user_auth($db, $id) {
     // remove connections
     run_sql($db, 'DELETE FROM connections WHERE auth_id=' . $id);
     // remove user auth record
-    $changes = delete_record($db, "user_auth", "id=" . $id);
+    $changes = delete_record($db, "user_auth", "id=?", $id);
     if ($changes) {
         $msg = "Deleting ip-record: " . $txt_record . "::Success!\n" . $msg;
     } else {
@@ -1249,7 +1249,7 @@ function delete_user_auth($db, $id) {
 function delete_user($db,$id)
 {
 //remove user record
-$changes = delete_record($db, "user_list", "id=" . $id);
+$changes = delete_record($db, "user_list", "id=?", $id);
 //if fail - exit
 if (!isset($changes) or empty($changes)) { return; }
 //remove auth records
@@ -1262,12 +1262,12 @@ $device = get_record($db, "devices", "user_id='$id'");
 if (!empty($device)) {
     LOG_INFO($db, "Delete device for user id: $id ".dump_record($db,'devices','user_id='.$id));
     unbind_ports($db, $device['id']);
-    run_sql($db, "DELETE FROM connections WHERE device_id=" . $device['id']);
-    run_sql($db, "DELETE FROM device_l3_interfaces WHERE device_id=" . $device['id']);
-    run_sql($db, "DELETE FROM device_ports WHERE device_id=" . $device['id']);
-    run_sql($db, "DELETE FROM device_filter_instances WHERE device_id=" . $device['id']);
-    run_sql($db, "DELETE FROM gateway_subnets WHERE device_id=".$device['id']);
-    delete_record($db, "devices", "id=" . $device['id']);
+    run_sql($db, "DELETE FROM connections WHERE device_id=?", $device['id']);
+    run_sql($db, "DELETE FROM device_l3_interfaces WHERE device_id=?", $device['id']);
+    run_sql($db, "DELETE FROM device_ports WHERE device_id=?", $device['id']);
+    run_sql($db, "DELETE FROM device_filter_instances WHERE device_id=?", $device['id']);
+    run_sql($db, "DELETE FROM gateway_subnets WHERE device_id=?",$device['id']);
+    delete_record($db, "devices", "id=?", $device['id']);
     }
 //remove auth assign rules
 run_sql($db, "DELETE FROM auth_rules WHERE user_id=$id");
@@ -1278,18 +1278,18 @@ function delete_device($db,$id)
 {
 LOG_INFO($db, "Try delete device id: $id ".dump_record($db,'devices','id='.$id));
 //remove user record
-$changes = delete_record($db, "devices", "id=" . $id);
+$changes = delete_record($db, "devices", "id=?", $id);
 //if fail - exit
 if (!isset($changes) or empty($changes)) {
     LOG_INFO($db,"Device id: $id has not been deleted");
     return;
     }
 unbind_ports($db, $id);
-run_sql($db, "DELETE FROM connections WHERE device_id=" . $id);
-run_sql($db, "DELETE FROM device_l3_interfaces WHERE device_id=" . $id);
-run_sql($db, "DELETE FROM device_ports WHERE device_id=" . $id);
-run_sql($db, "DELETE FROM device_filter_instances WHERE device_id=" . $id);
-run_sql($db, "DELETE FROM gateway_subnets WHERE device_id=".$id);
+run_sql($db, "DELETE FROM connections WHERE device_id=?", $id);
+run_sql($db, "DELETE FROM device_l3_interfaces WHERE device_id=?", $id);
+run_sql($db, "DELETE FROM device_ports WHERE device_id=?", $id);
+run_sql($db, "DELETE FROM device_filter_instances WHERE device_id=?", $id);
+run_sql($db, "DELETE FROM gateway_subnets WHERE device_id=?",$id);
 return $changes;
 }
 

+ 8 - 0
install-eye.sh

@@ -592,6 +592,12 @@ install_source_code() {
         chmod 750 /opt/Eye/scripts
         chmod 770 /opt/Eye/scripts/log
         chown -R eye:eye /opt/Eye/scripts
+
+        if [[ -f "/opt/Eye/docs/systemd/stat-sync.service" ]]; then
+            cp /opt/Eye/docs/systemd/stat-sync.service /etc/systemd/system/
+            systemctl enable stat-sync.service
+        fi
+
     fi
 
     # Применяем патч (только если установлен бэкенд, т.к. касается SNMP в Perl)
@@ -1298,6 +1304,8 @@ setup_dhcp_server() {
     # Copy systemd services
     if [[ -f "/opt/Eye/docs/systemd/dhcp-log.service" ]]; then
         cp /opt/Eye/docs/systemd/dhcp-log.service /etc/systemd/system/
+        mkdir -p /etc/systemd/system/dnsmasq.service.d
+        cp -f /opt/Eye/docs/systemd/dnsmasq.service.d/override.conf /etc/systemd/system/dnsmasq.service.d
     fi
 
     if [[ -f "/opt/Eye/docs/systemd/dhcp-log-truncate.service" ]]; then

+ 42 - 18
scripts/eye-statd.pl

@@ -109,7 +109,7 @@ InitSubnets();
 init_option($hdb);
 
 #a directory for storing traffic details in text form
-$save_path = get_option($dbh,72);
+$save_path = get_option($hdb,72);
 
 #the period for resetting statistics from netflow to billing
 $timeshift = get_option($hdb,55)*60;
@@ -476,11 +476,12 @@ my $pid = fork();
 
 INIT();
 
-#log_debug("ROUTERS-SVI:".Dumper(\%routers_svi));
-#log_debug("ROUTERS by IP::".Dumper(\%routers_by_ip));
-#log_debug("ROUTERS:".Dumper(\%routers));
-#log_debug("WAN-DEVS:".Dumper(\%wan_dev));
-#log_debug("LAN-DEVS:".Dumper(\%lan_dev));
+log_verbose("Start flush traffic to DB");
+log_debug("ROUTERS-SVI:".Dumper(\%routers_svi));
+log_debug("ROUTERS by IP::".Dumper(\%routers_by_ip));
+log_debug("ROUTERS:".Dumper(\%routers));
+log_debug("WAN-DEVS:".Dumper(\%wan_dev));
+log_debug("LAN-DEVS:".Dumper(\%lan_dev));
 
 if (!defined $pid) {
     $saving = 0;
@@ -511,6 +512,8 @@ my %routers_found;
 my $last_time = time();
 my $start_time;
 
+log_debug("Netflow statistics calculation started");
+
 foreach my $traf_record (@flush_table) {
 
 #log_debug("RAW-DATA: ".hash_to_kv_csv($traf_record));
@@ -538,7 +541,7 @@ if (!$start_time) { $start_time = $traf_record->{starttime}; }
 if (!$traf_record->{snmp_out} or !$traf_record->{snmp_in}) {
     #input
     if (!$traf_record->{snmp_out} and exists $routers_svi{$router_id}{$traf_record->{snmp_in}}{$traf_record->{dst_ip}}) {
-#        log_debug("ROUTER id: $router_id I-DATA: ".hash_to_kv_csv($traf_record));
+        #log_debug("ROUTER id: $router_id I-DATA: ".hash_to_kv_csv($traf_record));
         #input
         if (!$free_networks->match_string($traf_record->{src_ip})) {
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_in}}{in}) {
@@ -551,7 +554,7 @@ if (!$traf_record->{snmp_out} or !$traf_record->{snmp_in}) {
 	}
     #output
     if (!$traf_record->{snmp_in} and exists $routers_svi{$router_id}{$traf_record->{snmp_out}}{$traf_record->{src_ip}}) {
-#        log_debug("ROUTER id: $router_id O-DATA: ".hash_to_kv_csv($traf_record));
+        #log_debug("ROUTER id: $router_id O-DATA: ".hash_to_kv_csv($traf_record));
         #output
         if (!$free_networks->match_string($traf_record->{dst_ip})) {
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_out}}{out}) {
@@ -562,7 +565,7 @@ if (!$traf_record->{snmp_out} or !$traf_record->{snmp_in}) {
             }
         next;
         }
-#    log_debug("ROUTER id: $router_id U-DATA: ".hash_to_kv_csv($traf_record));
+    #log_debug("ROUTER id: $router_id U-DATA: ".hash_to_kv_csv($traf_record));
     #unknown packet
     next;
     }
@@ -570,7 +573,7 @@ if (!$traf_record->{snmp_out} or !$traf_record->{snmp_in}) {
 #simple output traffic from router
 if (exists $wan_dev{$router_id}->{$traf_record->{snmp_out}} and exists $wan_dev{$router_id}->{$traf_record->{snmp_in}}) {
     if (exists $routers_svi{$router_id}{$traf_record->{snmp_out}}{$traf_record->{src_ip}}) {
-#        log_debug("ROUTER id: $router_id O-SDATA: ".hash_to_kv_csv($traf_record));
+        #log_debug("ROUTER id: $router_id O-SDATA: ".hash_to_kv_csv($traf_record));
         #output
         if (!$free_networks->match_string($traf_record->{dst_ip})) {
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_out}}{out}) {
@@ -583,7 +586,7 @@ if (exists $wan_dev{$router_id}->{$traf_record->{snmp_out}} and exists $wan_dev{
         }
     #It is unlikely that it will ever work out
     if (exists $routers_svi{$router_id}{$traf_record->{snmp_in}}{$traf_record->{dst_ip}}) {
-#        log_debug("ROUTER id: $router_id I-SDATA: ".hash_to_kv_csv($traf_record));
+        #log_debug("ROUTER id: $router_id I-SDATA: ".hash_to_kv_csv($traf_record));
         #input
         if (!$free_networks->match_string($traf_record->{src_ip})) {
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_in}}{in}) {
@@ -594,14 +597,14 @@ if (exists $wan_dev{$router_id}->{$traf_record->{snmp_out}} and exists $wan_dev{
             }
         next;
         }
-#    log_debug("ROUTER id: $router_id U-SDATA: ".hash_to_kv_csv($traf_record));
+    #log_debug("ROUTER id: $router_id U-SDATA: ".hash_to_kv_csv($traf_record));
     #unknown packet
     next;
     } else {
     #forward
     if (!$free_networks->match_string($traf_record->{src_ip}) and !$free_networks->match_string($traf_record->{dst_ip})) {
         if ($traf_record->{direction}) {
-#	    log_debug("ROUTER id: $router_id FO-DATA: ".hash_to_kv_csv($traf_record));
+            #log_debug("ROUTER id: $router_id FO-DATA: ".hash_to_kv_csv($traf_record));
             #out
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_out}}{forward_out}) {
                 $wan_stats{$router_id}{$traf_record->{snmp_out}}{forward_out}+=$traf_record->{octets};
@@ -609,7 +612,7 @@ if (exists $wan_dev{$router_id}->{$traf_record->{snmp_out}} and exists $wan_dev{
                 $wan_stats{$router_id}{$traf_record->{snmp_out}}{forward_out}+=$traf_record->{octets};
                 }
             } else {
-#	    log_debug("ROUTER id: $router_id FI-DATA: ".hash_to_kv_csv($traf_record));
+            #log_debug("ROUTER id: $router_id FI-DATA: ".hash_to_kv_csv($traf_record));
             #in
             if (exists $wan_stats{$router_id}{$traf_record->{snmp_in}}{forward_in}) {
                 $wan_stats{$router_id}{$traf_record->{snmp_in}}{forward_in}+=$traf_record->{octets};
@@ -618,7 +621,7 @@ if (exists $wan_dev{$router_id}->{$traf_record->{snmp_out}} and exists $wan_dev{
                 }
             }
         } else {
-#	    log_debug("ROUTER id: $router_id FREE-DATA: ".hash_to_kv_csv($traf_record));
+        #log_debug("ROUTER id: $router_id FREE-DATA: ".hash_to_kv_csv($traf_record));
 	}
     }
 
@@ -699,7 +702,7 @@ if ($traf_record->{direction}) {
     }
 
 if (!$user_ip) {
-    log_debug("Unknown USER: ".hash_to_kv_csv($traf_record));
+    #log_debug("Unknown USER: ".hash_to_kv_csv($traf_record));
     next;
     }
 
@@ -729,6 +732,8 @@ push @detail_traffic, [
     ];
 }
 
+log_debug("The netflow statistics calculation is finished");
+
 @flush_table=();
 
 #start hour
@@ -737,6 +742,7 @@ my ($sec,$min,$hour,$day,$month,$year) = (localtime($last_time))[0,1,2,3,4,5];
 #save netflow
 if ($config_ref{save_detail}) {
     $save_path=~s/\/$//;
+    log_debug("Write netflow started");
     foreach my $dev_id (keys %saved_netflow) {
         my $netflow_file_path = $save_path.'/'.$dev_id.'/'.sprintf "%04d/%02d/%02d/%02d/",$year+1900,$month+1,$day,$hour;
         my $nmin = int($min/10)*10;
@@ -760,6 +766,7 @@ if ($config_ref{save_detail}) {
             @{$saved_netflow{$dev_id}}=();
             }
         }
+    log_debug("Write netflow is finished");
     }
 undef %saved_netflow;
 
@@ -779,6 +786,8 @@ my @batch_wan_stats=();
 
 #log_debug("User STATS: ".Dumper(\%user_stats));
 
+log_debug("The user statistics calculation started");
+
 # update database
 foreach my $user_ip (keys %user_stats) {
     next if (!exists $user_stats{$user_ip}{last_found});
@@ -853,9 +862,11 @@ foreach my $user_ip (keys %user_stats) {
 	}
     }
 
+log_debug("User calculation is finished");
 #print Dumper(\%wan_stats) if ($debug);
 
 # update database
+log_debug("Routers statistics started");
 foreach my $router_id (keys %wan_stats) {
     #last flow for user
     my ($sec,$min,$hour,$day,$month,$year) = (localtime($start_time))[0,1,2,3,4,5];
@@ -881,21 +892,32 @@ foreach my $router_id (keys %wan_stats) {
         ];
 	}
     }
+log_debug("Router statistics is finished");
 
+log_verbose("Try update user_auth table for ".scalar @batch_auth_status. " records");
 my $tSQL="UPDATE user_auth SET arp_found= ?, last_found= ? WHERE id= ?";
 batch_db_sql_cached($tSQL,\@batch_auth_status);
+log_verbose("Finished");
 
+log_verbose("Try update user_stats_full table for ".scalar @batch_user_stats_full. " records");
 $tSQL="INSERT INTO user_stats_full (ts,auth_id,router_id,byte_in,byte_out,pkt_in,pkt_out,step) VALUES( ?, ?, ?, ?, ?, ?, ?, ?)";
 batch_db_sql_cached($tSQL,\@batch_user_stats_full);
+log_verbose("Finished");
 
+log_verbose("Try create new records in  user_stats table for ".scalar @batch_user_stats. " records");
 $tSQL="INSERT INTO user_stats (ts,auth_id,router_id,byte_in,byte_out,pkt_in,pkt_out,step) VALUES( ?, ?, ?, ?, ?, ?, ? ,?)";
 batch_db_sql_cached($tSQL,\@batch_user_stats);
+log_verbose("Finished");
 
+log_verbose("Try update user_stats table for ".scalar @batch_user_stats_update. " records");
 $tSQL="UPDATE user_stats SET byte_in= ?, byte_out= ?, pkt_in = ?, pkt_out = ? WHERE ts = ? AND auth_id= ? AND router_id= ?";
 batch_db_sql_cached($tSQL,\@batch_user_stats_update);
+log_verbose("Finished");
 
+log_verbose("Try create new records in wan_stats table for ".scalar @batch_wan_stats. " records");
 $tSQL="INSERT INTO wan_stats (ts,router_id,interface_id,bytes_in,bytes_out,forward_in,forward_out) VALUES( ?, ?, ?, ?, ?, ?, ?)";
 batch_db_sql_cached($tSQL,\@batch_wan_stats);
+log_verbose("Finished");
 
 @batch_user_stats=();
 @batch_user_stats_update=();
@@ -904,9 +926,9 @@ batch_db_sql_cached($tSQL,\@batch_wan_stats);
 @batch_wan_stats=();
 
 if ($config_ref{enable_quotes}) {
-    db_log_debug($hdb,"Recalc quotes started");
+    log_info($hdb,"Recalc quotes started");
     foreach my $router_id (keys %routers_found) { recalc_quotes($hdb,$router_id); }
-    db_log_debug($hdb,"Recalc quotes stopped");
+    log_info($hdb,"Recalc quotes stopped");
     }
 
 if (scalar(@detail_traffic)) {
@@ -922,6 +944,8 @@ $hdb->disconnect();
 
 $saving = 0;
 
+log_verbose("Flush traffic to DB is finished");
+
 exit;
 }
 

+ 1 - 1
scripts/eyelib/common.pm

@@ -156,7 +156,7 @@ sub delete_user_auth {
     # Формируем идентификатор для лога
     my $auth_ident = $record->{ip} // '';
     if ($record->{dns_name}) {
-        $auth_ident .= '[' . $record->{dns_name} . ']';
+        $auth_ident .= ' [' . $record->{dns_name} . ']';
     }
     if ($record->{description}) {
         $auth_ident .= ' :: ' . $record->{description};

+ 359 - 278
scripts/eyelib/database.pm

@@ -4,6 +4,22 @@ package eyelib::database;
 # Copyright (C) Roman Dmitriev, rnd@rajven.ru
 #
 
+# commit example
+# Начинаем транзакцию вручную
+#$db->{AutoCommit} = 0;
+#eval {
+#    for my $row (@rows) {
+#        insert_record($db, 'user_auth', $row);
+#        insert_record($db, 'user_auth_alias', $row2);
+#    }
+#    $db->commit();
+#};
+#if ($@) {
+#    eval { $db->rollback(); };
+#    die "Migration failed: $@";
+#}
+#$db->{AutoCommit} = 1;
+
 use utf8;
 use open ":encoding(utf8)";
 use strict;
@@ -266,23 +282,32 @@ return $res;
 }
 
 #---------------------------------------------------------------------------------------------------------------
+
 sub batch_db_sql_cached {
-    my ($sql, $data) = @_;
+    my ( $sql, $data) = @_;
     my $db=init_db();
+    # Запоминаем исходное состояние AutoCommit
+    my $original_autocommit = $db->{AutoCommit};
     eval {
-        my $sth = $db->prepare_cached($sql) or die "Unable to prepare SQL: " . $db->errstr;
+        # Выключаем AutoCommit для транзакции
+        $db->{AutoCommit} = 0;
+        my $sth = $db->prepare_cached($sql)  or die "Unable to prepare SQL: " . $db->errstr;
         for my $params (@$data) {
             next unless @$params;
             $sth->execute(@$params) or die "Unable to execute with params [" . join(',', @$params) . "]: " . $sth->errstr;
         }
-        $db->commit() if (!$db->{AutoCommit});
+        $db->commit();
         1;
     } or do {
         my $err = $@ || 'Unknown error';
         eval { $db->rollback() };
-        $db->disconnect();
-        die "batch_db_sql_cached failed: $err";
+        warn "batch_sql_cached failed: $err";
+        # Восстанавливаем AutoCommit даже при ошибке
+        $db->{AutoCommit} = $original_autocommit;
+        return 0;
     };
+    # Восстанавливаем исходный режим AutoCommit
+    $db->{AutoCommit} = $original_autocommit;
     $db->disconnect();
     return 1;
 }
@@ -312,6 +337,9 @@ sub batch_db_sql_csv {
 
     my $db = init_db();
 
+    my $original_autocommit = $db->{AutoCommit};
+    $db->{AutoCommit} = 0;
+
     if (get_db_type($db) eq 'mysql') {
         # --- MySQL: попытка LOAD DATA, fallback на INSERT ---
         log_debug("Using LOAD DATA LOCAL INFILE for MySQL");
@@ -330,13 +358,12 @@ sub batch_db_sql_csv {
         }) or do {
             my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
             log_error($err);
+            $db->{AutoCommit} = $original_autocommit;
             $db->disconnect();
             return 0;
         };
-
         # Пишем заголовок
         $csv->print($fh, \@columns);
-
         # Пишем данные
         for my $row (@$data_rows) {
             next unless $row && ref($row) eq 'ARRAY' && @$row == @columns;
@@ -344,10 +371,8 @@ sub batch_db_sql_csv {
             $csv->print($fh, \@vals);
         }
         close $fh;
-
         my $col_list = join(', ', map { $db->quote_identifier($_) } @columns);
         my $query = qq{LOAD DATA LOCAL INFILE '$fname' INTO TABLE $table FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\r\n' IGNORE 1 LINES ($col_list)};
-
         my $load_ok = eval { $db->do($query); 1 };
         if (!$load_ok) {
             my $err = "MySQL LOAD DATA failed: $@";
@@ -355,6 +380,8 @@ sub batch_db_sql_csv {
             log_debug("Falling back to bulk INSERT for MySQL");
             goto FALLBACK_INSERT_MYSQL;
         }
+        $db->commit();
+        $db->{AutoCommit} = $original_autocommit;
 
         $db->disconnect();
         return 1;
@@ -378,12 +405,17 @@ sub batch_db_sql_csv {
                 1;
             };
 
-            if (!$success) {
+            if ($success) {
+                $db->commit();
+            } else {
+                eval { $db->rollback(); };
                 my $err = "MySQL bulk INSERT failed: $@";
                 log_error($err);
+                $db->{AutoCommit} = $original_autocommit;
                 $db->disconnect();
                 return 0;
             }
+            $db->{AutoCommit} = $original_autocommit;
         }
 
     } elsif (get_db_type($db) eq 'pg') {
@@ -425,6 +457,7 @@ sub batch_db_sql_csv {
             my $err = "Cannot create Text::CSV: " . Text::CSV->error_diag();
             log_error($err);
             eval { $db->pg_putcopyend(); };
+            $db->{AutoCommit} = $original_autocommit;
             $db->disconnect();
             return 0;
         };
@@ -445,15 +478,14 @@ sub batch_db_sql_csv {
         };
 
         if ($success) {
-            $db->disconnect();
-            return 1;
+            $db->commit();
         } else {
+            eval { $db->rollback(); };
             my $err = "CSV COPY failed: $@";
             log_error($err);
             eval { $db->pg_putcopyend(); };
             goto FALLBACK_INSERT_PG;
         }
-
         # ========================
         # Fallback для PostgreSQL
         # ========================
@@ -473,9 +505,13 @@ sub batch_db_sql_csv {
                 1;
             };
 
-            if (!$success) {
+            if ($success) {
+                $db->commit();
+            } else {
+                eval { $db->rollback(); };
                 my $err = "PostgreSQL bulk INSERT failed: $@";
                 log_error($err);
+                $db->{AutoCommit} = $original_autocommit;
                 $db->disconnect();
                 return 0;
             }
@@ -484,44 +520,49 @@ sub batch_db_sql_csv {
     } else {
         my $err = "Unsupported DBTYPE: ". get_db_type($db);
         log_error($err);
+        $db->{AutoCommit} = $original_autocommit;
         $db->disconnect();
         return 0;
     }
 
+    $db->{AutoCommit} = $original_autocommit;
     $db->disconnect();
     return 1;
 }
 #---------------------------------------------------------------------------------------------------------------
 
 sub reconnect_db {
-my $db_ref = shift;
-# Если соединение активно, ничего не делаем
-if ($$db_ref && $$db_ref->ping) {
-    return 1;
-    }
-# Переподключаемся
-eval {
-# Закрываем старое соединение если есть
-if ($$db_ref) {
-    $$db_ref->disconnect;
-    $$db_ref = undef;
+    my $db_ref = shift;
+
+    # Если соединение активно — ничего не делаем
+    if ($$db_ref && $$db_ref->ping) {
+        return 1;
     }
-# Создаем новое соединение
-$$db_ref = init_db();
-# Проверяем что соединение установлено
-unless ($$db_ref && $$db_ref->ping) {
-    die "Failed to establish database connection";
+
+    # Сохраняем AutoCommit из текущего соединения (если есть)
+    my $original_autocommit = 1;
+    if ($$db_ref) {
+        $original_autocommit = $$db_ref->{AutoCommit};
+        eval { $$db_ref->disconnect; };
+        $$db_ref = undef;
     }
-1;  # возвращаем истину при успехе
-} or do {
-    my $error = $@ || 'Unknown error';
-    warn "Database reconnection failed: $error";
-    $$db_ref = undef;
-    return 0;
+
+    # Пытаемся переподключиться
+    eval {
+        $$db_ref = init_db($original_autocommit);
+        unless ($$db_ref && $$db_ref->ping) {
+            log_die "Failed to establish database connection";
+        }
+        1;
+    } or do {
+        my $error = $@ || 'Unknown error';
+        $$db_ref = undef;
+        log_die "Database reconnection failed: $error";
+        return 0;
     };
-return 1;
-}
 
+    return 1;
+}
 
 #---------------------------------------------------------------------------------------------------------------
 
@@ -603,19 +644,28 @@ if ($log_level >= $L_WARNING) { write_db_log($db,$msg,$L_WARNING,$id); }
 #---------------------------------------------------------------------------------------------------------------
 
 sub init_db {
-# Create new database handle. If we can't connect, die()
-my $db;
-if ($config_ref{DBTYPE} eq 'mysql') {
-$db = DBI->connect("dbi:mysql:database=$DBNAME;host=$DBHOST;mysql_local_infile=1","$DBUSER","$DBPASS", 
-    { RaiseError => 0, AutoCommit => 1, mysql_enable_utf8 => 1 });
-if ( !defined $db ) { die "Cannot connect to MySQL server: $DBI::errstr\n"; }
-$db->do('SET NAMES utf8mb4');
-} else {
-$db = DBI->connect("dbi:Pg:dbname=$DBNAME;host=$DBHOST","$DBUSER","$DBPASS",
-    { RaiseError => 0, AutoCommit => 1, pg_enable_utf8 => 1, pg_server_prepare => 0 });
-if ( !defined $db ) { die "Cannot connect to PostgreSQL server: $DBI::errstr\n"; }
-}
-return $db;
+    my $autocommit = shift;
+    if (!defined $autocommit) { $autocommit = 1; }
+    my $db;
+    if ($config_ref{DBTYPE} eq 'mysql') {
+        $db = DBI->connect(
+            "dbi:mysql:database=$DBNAME;host=$DBHOST;port=3306;mysql_local_infile=1", $DBUSER, $DBPASS,
+            { RaiseError => 0, AutoCommit => $autocommit, mysql_enable_utf8 => 1 }
+        );
+        if (!defined $db) {
+            log_die "Cannot connect to MySQL server: $DBI::errstr\n";
+        }
+        $db->do('SET NAMES utf8mb4');
+    } else {
+        $db = DBI->connect(
+            "dbi:Pg:dbname=$DBNAME;host=$DBHOST;port=5432", $DBUSER, $DBPASS,
+            { RaiseError => 0, AutoCommit => $autocommit, pg_enable_utf8 => 1, pg_server_prepare => 0 }
+        );
+        if (!defined $db) {
+            log_die "Cannot connect to PostgreSQL server: $DBI::errstr\n";
+        }
+    }
+    return $db;
 }
 
 #---------------------------------------------------------------------------------------------------------------
@@ -644,33 +694,94 @@ sub get_option {
 
 #---------------------------------------------------------------------------------------------------------------
 
-sub do_sql {
-    my ($db, $sql, @bind_values) = @_;
-    return 0 unless $db && $sql;
-
-    my $mode;
-    if ($sql =~ /^\s*insert\b/i) {
-        $mode = 'id';
-    } elsif ($sql =~ /^\s*select\b/i) {
-        $mode = 'arrayref';
-    } else {
-        $mode = 'execute';
+sub get_records_sql {
+my ($db, $sql, @params) = @_;
+my @result;
+return @result if (!$db);
+return @result if (!$sql);
+unless (reconnect_db(\$db)) {
+    log_error("No database connection available");
+    return @result;
+    }
+my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
+if (ref($result_ref) eq 'ARRAY') {
+        @result = @$result_ref;
     }
+return @result;
+}
 
-    my $result = _execute_param($db, $sql, \@bind_values, { mode => $mode });
+#---------------------------------------------------------------------------------------------------------------
 
-    # Обработка ошибок: если _execute_param вернул undef/ложь — возвращаем 0
-    unless (defined $result) {
-        return 0;
+sub get_record_sql {
+my ($db, $sql, @params) = @_;
+my @result;
+return @result if (!$db);
+return @result if (!$sql);
+# Добавляем LIMIT только если его еще нет в запросе
+if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
+        $sql .= ' LIMIT 1';
     }
+# Переподключение
+unless (reconnect_db(\$db)) {
+    log_error("No database connection available");
+    return;
+    }
+return _execute_param($db, $sql, \@params, { mode => 'single' });
+}
 
-    if ($mode eq 'id') {
-        return $result;               # уже число (или 0)
-    } elsif ($mode eq 'arrayref') {
-        return ref($result) eq 'ARRAY' ? $result : 0;  # на случай ошибки
-    } else {
-        return $result ? 1 : 0;
+#---------------------------------------------------------------------------------------------------------------
+
+sub get_count_records {
+my ($db, $table, $filter, @params) = @_;
+my $result = 0;
+return $result if (!$db);
+return $result if (!$table);
+my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
+if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
+my $record = get_record_sql($db,$sSQL, @params);
+if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
+return $result;
+}
+
+#---------------------------------------------------------------------------------------------------------------
+
+sub get_id_record {
+my ($db, $table, $filter, @params) = @_;
+my $result = 0;
+return $result if (!$db);
+return $result if (!$table);
+my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
+if ($record->{id}) { $result = $record->{id}; }
+return $result;
+}
+
+#---------------------------------------------------------------------------------------------------------------
+
+sub get_diff_rec {
+my ($db, $table, $record, $filter_sql, @filter_params) = @_;
+return unless $db && $table && $filter_sql;
+
+unless (reconnect_db(\$db)) {
+    log_error("No database connection available");
+    return;
+    }
+my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
+return unless $old_record;
+my $result;
+foreach my $field (keys %$record) {
+    if (!$record->{$field}) { $record->{$field}=''; }
+    if (!$old_record->{$field}) { $old_record->{$field}=''; }
+    if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
     }
+return hash_to_text($result);
+}
+
+#---------------------------------------------------------------------------------------------------------------
+
+sub get_db_type {
+my $db = shift;
+return lc($db->{Driver}->{Name});
+#'mysql', 'pg'
 }
 
 #---------------------------------------------------------------------------------------------------------------
@@ -682,29 +793,38 @@ sub _execute_param {
 
     my $mode = $options->{mode} || 'execute';
 
-    # автоматическая поддержка RETURNING для PostgreSQL ---
+    # --- Автоматическая поддержка RETURNING для PostgreSQL ---
     my $was_modified = 0;
     my $original_sql = $sql;
     if ($mode eq 'id' && $sql =~ /^\s*INSERT\b/i) {
         if (get_db_type($db) eq 'pg') {
-            # Добавляем RETURNING id, если его ещё нет
             unless ($sql =~ /\bRETURNING\b/i) {
                 $sql .= ' RETURNING id';
                 $was_modified = 1;
-                # Теперь нам нужно получить скаляр, а не использовать lastval()
-                $mode = 'scalar';  # временно меняем режим
+                $mode = 'scalar';
             }
         }
     }
 
-    # Логируем не-SELECT (но уже с возможным RETURNING)
+    # Логируем не-SELECT
     unless ($original_sql =~ /^\s*SELECT/i) {
         log_debug($original_sql . ($params ? ' | params: [' . join(', ', map { defined $_ ? $_ : 'undef' } @$params) . ']' : ''));
     }
 
-    unless (reconnect_db(\$db)) {
-        log_error("No database connection available");
-        return wantarray ? () : undef;
+    # === не переподключаемся внутри транзакции ===
+    my $autocommit_enabled = $db->{AutoCommit};
+    unless ($autocommit_enabled) {
+        # В транзакции: нельзя переподключаться!
+        unless ($db->ping) {
+            log_error("Database connection lost during transaction");
+            return wantarray ? () : undef;
+        }
+    } else {
+        # Вне транзакции: можно переподключиться
+        unless (reconnect_db(\$db)) {
+            log_error("No database connection available");
+            return wantarray ? () : undef;
+        }
     }
 
     my $sth = $db->prepare($sql) or do {
@@ -722,7 +842,6 @@ sub _execute_param {
 
     # --- Обработка результатов ---
     if ($was_modified && $mode eq 'scalar') {
-        # Это был INSERT + RETURNING id в PostgreSQL
         my $row = $sth->fetchrow_arrayref();
         $sth->finish();
         my $id = $row ? $row->[0] : 0;
@@ -752,7 +871,6 @@ sub _execute_param {
         return $row ? $row->[0] : undef;
     }
     elsif ($mode eq 'id') {
-        # Сюда попадём только если НЕ было модификации (т.е. MySQL или старый Pg-путь)
         if ($original_sql =~ /^\s*INSERT/i) {
             my $id;
             if (get_db_type($db) eq 'mysql') {
@@ -771,107 +889,57 @@ sub _execute_param {
         return 1;
     }
 }
-#---------------------------------------------------------------------------------------------------------------
-
-sub get_records_sql {
-my ($db, $sql, @params) = @_;
-my @result;
-return @result if (!$db);
-return @result if (!$sql);
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return @result;
-    }
-my $result_ref = _execute_param($db, $sql, \@params, { mode => 'array' });
-if (ref($result_ref) eq 'ARRAY') {
-        @result = @$result_ref;
-    }
-return @result;
-}
-
-#---------------------------------------------------------------------------------------------------------------
-
-sub get_record_sql {
-my ($db, $sql, @params) = @_;
-my @result;
-return @result if (!$db);
-return @result if (!$sql);
-# Добавляем LIMIT только если его еще нет в запросе
-if ($sql !~ /\bLIMIT\s+\d+/i && $sql !~ /\bFETCH\s+FIRST\s+\d+/i) {
-        $sql .= ' LIMIT 1';
-    }
-# Переподключение
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return;
-    }
-return _execute_param($db, $sql, \@params, { mode => 'single' });
-}
 
 #---------------------------------------------------------------------------------------------------------------
 
-sub get_count_records {
-my ($db, $table, $filter, @params) = @_;
-my $result = 0;
-return $result if (!$db);
-return $result if (!$table);
-my $sSQL='SELECT COUNT(*) as rec_cnt FROM '.$table;
-if ($filter) { $sSQL=$sSQL." WHERE ".$filter; }
-my $record = get_record_sql($db,$sSQL, @params);
-if ($record->{rec_cnt}) { $result = $record->{rec_cnt}; }
-return $result;
-}
-
-#---------------------------------------------------------------------------------------------------------------
-
-sub get_id_record {
-my ($db, $table, $filter, @params) = @_;
-my $result = 0;
-return $result if (!$db);
-return $result if (!$table);
-my $record = get_record_sql($db,"SELECT id FROM $table WHERE $filter", @params);
-if ($record->{id}) { $result = $record->{id}; }
-return $result;
-}
+sub do_sql {
+    my ($db, $sql, @bind_values) = @_;
+    return unless $db && $sql;  # Возвращаем undef при ошибке входных данных
 
-#---------------------------------------------------------------------------------------------------------------
+    my $mode;
+    if ($sql =~ /^\s*insert\b/i) {
+        $mode = 'id';
+    } elsif ($sql =~ /^\s*select\b/i) {
+        $mode = 'arrayref';
+    } else {
+        $mode = 'execute';
+    }
 
-sub get_diff_rec {
-my ($db, $table, $record, $filter_sql, @filter_params) = @_;
-return unless $db && $table && $filter_sql;
+    my $result = _execute_param($db, $sql, \@bind_values, { mode => $mode });
 
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return;
-    }
-my $old_record = get_record_sql($db,"SELECT * FROM $table WHERE $filter_sql",@filter_params);
-return unless $old_record;
-my $result;
-foreach my $field (keys %$record) {
-    if (!$record->{$field}) { $record->{$field}=''; }
-    if (!$old_record->{$field}) { $old_record->{$field}=''; }
-    if ($record->{$field}!~/^$old_record->{$field}$/) { $result->{$field} = "$record->{$field} [ old: " . $old_record->{$field} . "]"; }
+    # Если _execute_param вернул undef/ложь — это ошибка
+    unless (defined $result) {
+        return;  # Возвращаем undef (лучше, чем 0)
     }
-return hash_to_text($result);
-}
-
-#---------------------------------------------------------------------------------------------------------------
 
-sub get_db_type {
-my $db = shift;
-return lc($db->{Driver}->{Name});
-#'mysql', 'pg'
+    if ($mode eq 'id') {
+        return $result;  # число (возможно 0 — допустимо для ID)
+    } elsif ($mode eq 'arrayref') {
+        # _execute_param всегда возвращает ARRAYREF при успехе
+        return $result;
+    } else {
+        # Для UPDATE/DELETE: возвращаем количество затронутых строк или 1
+        return $result ? $result : 1;
+    }
 }
 
 #---------------------------------------------------------------------------------------------------------------
 
 sub insert_record {
 my ($db, $table, $record) = @_;
-return unless $db && $table;
+return unless $db && $table && ref($record) eq 'HASH' && %$record;
 
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return;
+# Переподключаемся ТОЛЬКО если не в транзакции
+if ($db->{AutoCommit}) {
+        unless (reconnect_db(\$db)) {
+            log_error("No database connection available");
+            return;
+        }
+    } else {
+        unless ($db->ping) {
+            log_error("Database connection lost during transaction");
+            return;
+        }
     }
 
 my $db_info= build_db_schema($db);
@@ -881,10 +949,10 @@ my $rec_id = 0;
 
 if ($table eq "user_auth") {
     foreach my $field (keys %$record) {
-	if (exists $acl_fields{$field}) { $record->{changed}="1"; }
-	if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
-	if (exists $dns_fields{$field}) { $dns_changed=1; }
-	}
+        if (exists $acl_fields{$field}) { $record->{changed}="1"; }
+        if (exists $dhcp_fields{$field}) { $record->{dhcp_changed}="1"; }
+        if (exists $dns_fields{$field}) { $dns_changed=1; }
+        }
     }
 
 my @insert_params;
@@ -916,36 +984,36 @@ if ($result) {
     $rec_id = $result if ($table eq "user_auth");
     $new_str='id: '.$result.' '.$new_str;
     if ($table eq 'user_auth_alias' and $dns_changed) {
-	if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
-	    my $add_dns;
-	    $add_dns->{'name_type'}='CNAME';
-	    $add_dns->{'name'}=$record->{'alias'};
-	    $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
-	    $add_dns->{'operation_type'}='add';
-	    $add_dns->{'auth_id'}=$record->{'auth_id'};
-	    insert_record($db,'dns_queue',$add_dns);
-	    }
-	}
+        if ($record->{'alias'} and $record->{'alias'}!~/\.$/) {
+            my $add_dns;
+            $add_dns->{'name_type'}='CNAME';
+            $add_dns->{'name'}=$record->{'alias'};
+            $add_dns->{'value'}=get_dns_name($db,$record->{'auth_id'});
+            $add_dns->{'operation_type'}='add';
+            $add_dns->{'auth_id'}=$record->{'auth_id'};
+            insert_record($db,'dns_queue',$add_dns);
+            }
+        }
     if ($table eq 'user_auth' and $dns_changed) {
-	if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
-	    my $add_dns;
-	    $add_dns->{'name_type'}='A';
-	    $add_dns->{'name'}=$record->{'dns_name'};
-	    $add_dns->{'value'}=$record->{'ip'};
-	    $add_dns->{'operation_type'}='add';
-	    $add_dns->{'auth_id'}=$result;
-	    insert_record($db,'dns_queue',$add_dns);
-	    }
-	if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
-	    my $add_dns;
-	    $add_dns->{'name_type'}='PTR';
-	    $add_dns->{'name'}=$record->{'dns_name'};
-	    $add_dns->{'value'}=$record->{'ip'};
-	    $add_dns->{'operation_type'}='add';
-	    $add_dns->{'auth_id'}=$result;
-	    insert_record($db,'dns_queue',$add_dns);
-	    }
-	}
+        if ($record->{'dns_name'} and $record->{'ip'} and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
+            my $add_dns;
+            $add_dns->{'name_type'}='A';
+            $add_dns->{'name'}=$record->{'dns_name'};
+            $add_dns->{'value'}=$record->{'ip'};
+            $add_dns->{'operation_type'}='add';
+            $add_dns->{'auth_id'}=$result;
+            insert_record($db,'dns_queue',$add_dns);
+            }
+        if ($record->{'dns_name'} and $record->{'ip'} and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
+            my $add_dns;
+            $add_dns->{'name_type'}='PTR';
+            $add_dns->{'name'}=$record->{'dns_name'};
+            $add_dns->{'value'}=$record->{'ip'};
+            $add_dns->{'operation_type'}='add';
+            $add_dns->{'auth_id'}=$result;
+            insert_record($db,'dns_queue',$add_dns);
+            }
+        }
     }
 db_log_debug($db,'Add record to table '.$table.' '.$new_str,$rec_id);
 return $result;
@@ -955,12 +1023,19 @@ return $result;
 
 sub update_record {
 my ($db, $table, $record, $filter_sql, @filter_params) = @_;
-
 return unless $db && $table && $filter_sql;
 
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return;
+# Переподключаемся ТОЛЬКО если не в транзакции
+if ($db->{AutoCommit}) {
+        unless (reconnect_db(\$db)) {
+            log_error("No database connection available");
+            return;
+        }
+    } else {
+        unless ($db->ping) {
+            log_error("Database connection lost during transaction");
+            return;
+        }
     }
 
 my $db_info = build_db_schema($db);
@@ -981,9 +1056,9 @@ if ($table eq "user_auth") {
     #disable update field 'created_by'
     #if ($old_record->{'created_by'} and exists ($record->{'created_by'})) { delete $record->{'created_by'}; }
     foreach my $field (keys %$record) {
-	if (exists $acl_fields{$field}) { $record->{changed}="1"; }
+        if (exists $acl_fields{$field}) { $record->{changed}="1"; }
         if (exists $dhcp_fields{$field} and !is_system_ou($db,$cur_ou_id)) { $record->{dhcp_changed}="1"; }
-	if (exists $dns_fields{$field}) { $dns_changed=1; }
+        if (exists $dns_fields{$field}) { $dns_changed=1; }
         }
     }
 
@@ -1010,72 +1085,72 @@ $set_clause =~ s/,\s*$//;
 $diff =~ s/,\s*$//;
 
 if ($table eq 'user_auth') {
-	if ($dns_changed) {
-	    my $del_dns;
-	    if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
-		    $del_dns->{'name_type'}='A';
-		    $del_dns->{'name'}=$old_record->{'dns_name'};
-		    $del_dns->{'value'}=$old_record->{'ip'};
-		    $del_dns->{'operation_type'}='del';
-		    if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
-		    insert_record($db,'dns_queue',$del_dns);
-		    }
-	    if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
-		    $del_dns->{'name_type'}='PTR';
-		    $del_dns->{'name'}=$old_record->{'dns_name'};
-		    $del_dns->{'value'}=$old_record->{'ip'};
-		    $del_dns->{'operation_type'}='del';
-		    if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
-		    insert_record($db,'dns_queue',$del_dns);
-		    }
-	    my $new_dns;
-	    my $dns_rec_ip = $old_record->{ip};
-	    my $dns_rec_name = $old_record->{dns_name};
-	    if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
-	    if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
-	    if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
-		$new_dns->{'name_type'}='A';
-		$new_dns->{'name'}=$dns_rec_name;
-		$new_dns->{'value'}=$dns_rec_ip;
-		$new_dns->{'operation_type'}='add';
-		if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
-		insert_record($db,'dns_queue',$new_dns);
-		}
-	    if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
-		$new_dns->{'name_type'}='PTR';
-		$new_dns->{'name'}=$dns_rec_name;
-		$new_dns->{'value'}=$dns_rec_ip;
-		$new_dns->{'operation_type'}='add';
-		if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
-		insert_record($db,'dns_queue',$new_dns);
-		}
-	    }
-	}
+        if ($dns_changed) {
+            my $del_dns;
+            if ($old_record->{'dns_name'} and $old_record->{'ip'} and !$old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
+                    $del_dns->{'name_type'}='A';
+                    $del_dns->{'name'}=$old_record->{'dns_name'};
+                    $del_dns->{'value'}=$old_record->{'ip'};
+                    $del_dns->{'operation_type'}='del';
+                    if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
+                    insert_record($db,'dns_queue',$del_dns);
+                    }
+            if ($old_record->{'dns_name'} and $old_record->{'ip'} and $old_record->{'dns_ptr_only'} and $old_record->{'dns_name'}!~/\.$/) {
+                    $del_dns->{'name_type'}='PTR';
+                    $del_dns->{'name'}=$old_record->{'dns_name'};
+                    $del_dns->{'value'}=$old_record->{'ip'};
+                    $del_dns->{'operation_type'}='del';
+                    if ($rec_id) { $del_dns->{'auth_id'}=$rec_id; }
+                    insert_record($db,'dns_queue',$del_dns);
+                    }
+            my $new_dns;
+            my $dns_rec_ip = $old_record->{ip};
+            my $dns_rec_name = $old_record->{dns_name};
+            if ($record->{'dns_name'}) { $dns_rec_name = $record->{'dns_name'}; }
+            if ($record->{'ip'}) { $dns_rec_ip = $record->{'ip'}; }
+            if ($dns_rec_name and $dns_rec_ip and !$record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
+                $new_dns->{'name_type'}='A';
+                $new_dns->{'name'}=$dns_rec_name;
+                $new_dns->{'value'}=$dns_rec_ip;
+                $new_dns->{'operation_type'}='add';
+                if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
+                insert_record($db,'dns_queue',$new_dns);
+                }
+            if ($dns_rec_name and $dns_rec_ip and $record->{'dns_ptr_only'} and $record->{'dns_name'}!~/\.$/) {
+                $new_dns->{'name_type'}='PTR';
+                $new_dns->{'name'}=$dns_rec_name;
+                $new_dns->{'value'}=$dns_rec_ip;
+                $new_dns->{'operation_type'}='add';
+                if ($rec_id) { $new_dns->{'auth_id'}=$rec_id; }
+                insert_record($db,'dns_queue',$new_dns);
+                }
+            }
+        }
 
 if ($table eq 'user_auth_alias') {
-	if ($dns_changed) {
-	    my $del_dns;
-	    if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
-	    $del_dns->{'name_type'}='CNAME';
-	    $del_dns->{'name'}=$old_record->{'alias'};
-	    $del_dns->{'operation_type'}='del';
-	    $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
-	    $del_dns->{'auth_id'}=$old_record->{auth_id};
-	    insert_record($db,'dns_queue',$del_dns);
-	    }
-	    my $new_dns;
-	    my $dns_rec_name = $old_record->{alias};
-	    if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
-	    if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
-		$new_dns->{'name_type'}='CNAME';
-		$new_dns->{'name'}=$dns_rec_name;
-		$new_dns->{'operation_type'}='add';
-		$new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
-		$new_dns->{'auth_id'}=$rec_id;
-		insert_record($db,'dns_queue',$new_dns);
-		}
-	    }
-	}
+        if ($dns_changed) {
+            my $del_dns;
+            if ($old_record->{'alias'} and $old_record->{'alias'}!~/\.$/) {
+            $del_dns->{'name_type'}='CNAME';
+            $del_dns->{'name'}=$old_record->{'alias'};
+            $del_dns->{'operation_type'}='del';
+            $del_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
+            $del_dns->{'auth_id'}=$old_record->{auth_id};
+            insert_record($db,'dns_queue',$del_dns);
+            }
+            my $new_dns;
+            my $dns_rec_name = $old_record->{alias};
+            if ($record->{'alias'}) { $dns_rec_name = $record->{'alias'}; }
+            if ($dns_rec_name and $record->{'alias'}!~/\.$/) {
+                $new_dns->{'name_type'}='CNAME';
+                $new_dns->{'name'}=$dns_rec_name;
+                $new_dns->{'operation_type'}='add';
+                $new_dns->{'value'}=get_dns_name($db,$old_record->{auth_id});
+                $new_dns->{'auth_id'}=$rec_id;
+                insert_record($db,'dns_queue',$new_dns);
+                }
+            }
+        }
 
 # Формируем полный список параметров: сначала SET, потом WHERE
 my @all_params = (@update_params, @filter_params);
@@ -1084,16 +1159,23 @@ db_log_debug($db, "Change table $table for $filter_sql set: $diff", $rec_id);
 return do_sql($db, $update_sql, @all_params);
 }
 
-
 #---------------------------------------------------------------------------------------------------------------
 
 sub delete_record {
 my ($db, $table, $filter_sql, @filter_params) = @_;
 return unless $db && $table && $filter_sql;
 
-unless (reconnect_db(\$db)) {
-    log_error("No database connection available");
-    return;
+# Переподключаемся ТОЛЬКО если не в транзакции
+if ($db->{AutoCommit}) {
+        unless (reconnect_db(\$db)) {
+            log_error("No database connection available");
+            return;
+        }
+    } else {
+        unless ($db->ping) {
+            log_error("Database connection lost during transaction");
+            return;
+        }
     }
 
 my $select_sql = "SELECT * FROM $table WHERE $filter_sql";
@@ -1420,10 +1502,9 @@ if ($MY_NAME!~/upgrade.pl/) {
     init_option($dbh);
     clean_variables($dbh);
     Set_Variable($dbh);
+    warn "DBI driver name: ", $dbh->{Driver}->{Name}, "\n" if ($debug);
+    warn "Full dbh class: ", ref($dbh), "\n" if ($debug);
     }
 
-#warn "DBI driver name: ", $dbh->{Driver}->{Name}, "\n";
-#warn "Full dbh class: ", ref($dbh), "\n";
-
 1;
 }

+ 1 - 1
scripts/eyelib/main.pm

@@ -217,7 +217,7 @@ sub log_die {
 wrlog($W_ERROR,$_[0]);
 my $worktime = time()-$BASETIME;
 log_info("Script work $worktime sec.");
-sendEmail("$HOSTNAME - $MY_NAME die! ","Process: $MY_NAME aborted with error:\n$_[0]");
+#sendEmail("$HOSTNAME - $MY_NAME die! ","Process: $MY_NAME aborted with error:\n$_[0]");
 die ($_[0]);
 }