|
|
@@ -24,468 +24,30 @@ use warnings;
|
|
|
|
|
|
my $chunk_count = 1000;
|
|
|
|
|
|
-my %pg_schema = (
|
|
|
- 'acl' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(30)',
|
|
|
- 'description_english' => 'VARCHAR(250)',
|
|
|
- 'description_russian' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'ad_comp_cache' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(63)',
|
|
|
- 'last_found' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'auth_rules' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'user_id' => 'INTEGER',
|
|
|
- 'ou_id' => 'INTEGER',
|
|
|
- 'rule_type' => 'SMALLINT',
|
|
|
- 'rule' => 'VARCHAR(40)',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'building' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(50)',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'config' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'option_id' => 'INTEGER',
|
|
|
- 'value' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'config_options' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'option_name' => 'VARCHAR(50)',
|
|
|
- 'description_russian' => 'TEXT',
|
|
|
- 'description_english' => 'TEXT',
|
|
|
- 'draft' => 'SMALLINT',
|
|
|
- 'uniq' => 'SMALLINT',
|
|
|
- 'option_type' => 'VARCHAR(100)',
|
|
|
- 'default_value' => 'VARCHAR(250)',
|
|
|
- 'min_value' => 'INTEGER',
|
|
|
- 'max_value' => 'INTEGER',
|
|
|
- },
|
|
|
- 'connections' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'device_id' => 'BIGINT',
|
|
|
- 'port_id' => 'BIGINT',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'last_found' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'customers' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'login' => 'VARCHAR(20)',
|
|
|
- 'description' => 'VARCHAR(100)',
|
|
|
- 'password' => 'VARCHAR(255)',
|
|
|
- 'api_key' => 'VARCHAR(255)',
|
|
|
- 'rights' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'devices' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'device_type' => 'INTEGER',
|
|
|
- 'device_model_id' => 'INTEGER',
|
|
|
- 'firmware' => 'VARCHAR(100)',
|
|
|
- 'vendor_id' => 'INTEGER',
|
|
|
- 'device_name' => 'VARCHAR(50)',
|
|
|
- 'building_id' => 'INTEGER',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'ip_int' => 'BIGINT',
|
|
|
- 'login' => 'VARCHAR(50)',
|
|
|
- 'password' => 'VARCHAR(255)',
|
|
|
- 'protocol' => 'SMALLINT',
|
|
|
- 'control_port' => 'INTEGER',
|
|
|
- 'port_count' => 'INTEGER',
|
|
|
- 'sn' => 'VARCHAR(80)',
|
|
|
- 'description' => 'VARCHAR(255)',
|
|
|
- 'snmp_version' => 'SMALLINT',
|
|
|
- 'snmp3_auth_proto' => 'VARCHAR(10)',
|
|
|
- 'snmp3_priv_proto' => 'VARCHAR(10)',
|
|
|
- 'snmp3_user_rw' => 'VARCHAR(20)',
|
|
|
- 'snmp3_user_rw_password' => 'VARCHAR(20)',
|
|
|
- 'snmp3_user_ro' => 'VARCHAR(20)',
|
|
|
- 'snmp3_user_ro_password' => 'VARCHAR(20)',
|
|
|
- 'community' => 'VARCHAR(50)',
|
|
|
- 'rw_community' => 'VARCHAR(50)',
|
|
|
- 'fdb_snmp_index' => 'SMALLINT',
|
|
|
- 'discovery' => 'SMALLINT',
|
|
|
- 'netflow_save' => 'SMALLINT',
|
|
|
- 'user_acl' => 'SMALLINT',
|
|
|
- 'dhcp' => 'SMALLINT',
|
|
|
- 'nagios' => 'SMALLINT',
|
|
|
- 'active' => 'SMALLINT',
|
|
|
- 'nagios_status' => 'VARCHAR(10)',
|
|
|
- 'queue_enabled' => 'SMALLINT',
|
|
|
- 'connected_user_only' => 'SMALLINT',
|
|
|
- 'user_id' => 'INTEGER',
|
|
|
- 'deleted' => 'SMALLINT',
|
|
|
- 'discovery_locked' => 'SMALLINT',
|
|
|
- 'locked_timestamp' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'device_filter_instances' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'instance_id' => 'INTEGER',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- },
|
|
|
- 'device_l3_interfaces' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- 'snmpin' => 'INTEGER',
|
|
|
- 'interface_type' => 'SMALLINT',
|
|
|
- 'name' => 'VARCHAR(100)',
|
|
|
- },
|
|
|
- 'device_models' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'model_name' => 'VARCHAR(200)',
|
|
|
- 'vendor_id' => 'INTEGER',
|
|
|
- 'poe_in' => 'SMALLINT',
|
|
|
- 'poe_out' => 'SMALLINT',
|
|
|
- 'nagios_template' => 'VARCHAR(200)',
|
|
|
- },
|
|
|
- 'device_ports' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- 'snmp_index' => 'INTEGER',
|
|
|
- 'port' => 'INTEGER',
|
|
|
- 'ifname' => 'VARCHAR(40)',
|
|
|
- 'port_name' => 'VARCHAR(40)',
|
|
|
- 'description' => 'VARCHAR(50)',
|
|
|
- 'target_port_id' => 'INTEGER',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'last_mac_count' => 'INTEGER',
|
|
|
- 'uplink' => 'SMALLINT',
|
|
|
- 'nagios' => 'SMALLINT',
|
|
|
- 'skip' => 'SMALLINT',
|
|
|
- 'vlan' => 'INTEGER',
|
|
|
- 'tagged_vlan' => 'VARCHAR(250)',
|
|
|
- 'untagged_vlan' => 'VARCHAR(250)',
|
|
|
- 'forbidden_vlan' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'device_types' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name_russian' => 'VARCHAR(50)',
|
|
|
- 'name_english' => 'VARCHAR(50)',
|
|
|
- },
|
|
|
- 'dhcp_log' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'mac' => 'MACADDR',
|
|
|
- 'ip_int' => 'BIGINT',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'action' => 'VARCHAR(10)',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'dhcp_hostname' => 'VARCHAR(250)',
|
|
|
- 'circuit_id' => 'VARCHAR(255)',
|
|
|
- 'remote_id' => 'VARCHAR(255)',
|
|
|
- 'client_id' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'dhcp_queue' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'mac' => 'MACADDR',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'action' => 'VARCHAR(10)',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'dhcp_hostname' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'dns_cache' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'dns' => 'VARCHAR(250)',
|
|
|
- 'ip' => 'BIGINT',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'dns_queue' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'auth_id' => 'INTEGER',
|
|
|
- 'name_type' => 'VARCHAR(10)',
|
|
|
- 'name' => 'VARCHAR(200)',
|
|
|
- 'operation_type' => 'VARCHAR(10)',
|
|
|
- 'value' => 'VARCHAR(100)',
|
|
|
- },
|
|
|
- 'filter_instances' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(50)',
|
|
|
- 'description' => 'VARCHAR(200)',
|
|
|
- },
|
|
|
- 'filter_list' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(50)',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- 'proto' => 'VARCHAR(10)',
|
|
|
- 'dst' => 'TEXT',
|
|
|
- 'dstport' => 'VARCHAR(20)',
|
|
|
- 'srcport' => 'VARCHAR(20)',
|
|
|
- 'filter_type' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'gateway_subnets' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- 'subnet_id' => 'INTEGER',
|
|
|
- },
|
|
|
- 'group_filters' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'group_id' => 'INTEGER',
|
|
|
- 'filter_id' => 'INTEGER',
|
|
|
- 'rule_order' => 'INTEGER',
|
|
|
- 'action' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'group_list' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'instance_id' => 'INTEGER',
|
|
|
- 'group_name' => 'VARCHAR(50)',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'mac_history' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'mac' => 'VARCHAR(12)',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'device_id' => 'BIGINT',
|
|
|
- 'port_id' => 'BIGINT',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'dhcp_hostname' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'mac_vendors' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'oui' => 'VARCHAR(20)',
|
|
|
- 'companyname' => 'VARCHAR(255)',
|
|
|
- 'companyaddress' => 'VARCHAR(255)',
|
|
|
- },
|
|
|
- 'ou' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'ou_name' => 'VARCHAR(40)',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- 'default_users' => 'SMALLINT',
|
|
|
- 'default_hotspot' => 'SMALLINT',
|
|
|
- 'nagios_dir' => 'VARCHAR(255)',
|
|
|
- 'nagios_host_use' => 'VARCHAR(50)',
|
|
|
- 'nagios_ping' => 'SMALLINT',
|
|
|
- 'nagios_default_service' => 'VARCHAR(100)',
|
|
|
- 'enabled' => 'SMALLINT',
|
|
|
- 'filter_group_id' => 'INTEGER',
|
|
|
- 'queue_id' => 'INTEGER',
|
|
|
- 'dynamic' => 'SMALLINT',
|
|
|
- 'life_duration' => 'DECIMAL(10,2)',
|
|
|
- 'parent_id' => 'INTEGER',
|
|
|
- },
|
|
|
- 'queue_list' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'queue_name' => 'VARCHAR(20)',
|
|
|
- 'download' => 'INTEGER',
|
|
|
- 'upload' => 'INTEGER',
|
|
|
- },
|
|
|
- 'remote_syslog' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'device_id' => 'BIGINT',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'message' => 'TEXT',
|
|
|
- },
|
|
|
- 'sessions' => {
|
|
|
- 'id' => 'VARCHAR(128)',
|
|
|
- 'data' => 'TEXT',
|
|
|
- 'last_accessed' => 'INTEGER',
|
|
|
- },
|
|
|
- 'subnets' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'subnet' => 'VARCHAR(18)',
|
|
|
- 'vlan_tag' => 'INTEGER',
|
|
|
- 'ip_int_start' => 'BIGINT',
|
|
|
- 'ip_int_stop' => 'BIGINT',
|
|
|
- 'dhcp_start' => 'BIGINT',
|
|
|
- 'dhcp_stop' => 'BIGINT',
|
|
|
- 'dhcp_lease_time' => 'INTEGER',
|
|
|
- 'gateway' => 'BIGINT',
|
|
|
- 'office' => 'SMALLINT',
|
|
|
- 'hotspot' => 'SMALLINT',
|
|
|
- 'vpn' => 'SMALLINT',
|
|
|
- 'free' => 'SMALLINT',
|
|
|
- 'dhcp' => 'SMALLINT',
|
|
|
- 'static' => 'SMALLINT',
|
|
|
- 'dhcp_update_hostname' => 'SMALLINT',
|
|
|
- 'discovery' => 'SMALLINT',
|
|
|
- 'notify' => 'SMALLINT',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- },
|
|
|
- 'traffic_detail' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'router_id' => 'INTEGER',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'proto' => 'SMALLINT',
|
|
|
- 'src_ip' => 'BIGINT',
|
|
|
- 'dst_ip' => 'BIGINT',
|
|
|
- 'src_port' => 'INTEGER',
|
|
|
- 'dst_port' => 'INTEGER',
|
|
|
- 'bytes' => 'BIGINT',
|
|
|
- 'pkt' => 'BIGINT',
|
|
|
- },
|
|
|
- 'unknown_mac' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'mac' => 'VARCHAR(12)',
|
|
|
- 'port_id' => 'BIGINT',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'user_auth' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'user_id' => 'BIGINT',
|
|
|
- 'ou_id' => 'INTEGER',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'ip_int' => 'BIGINT',
|
|
|
- 'save_traf' => 'SMALLINT',
|
|
|
- 'enabled' => 'SMALLINT',
|
|
|
- 'dhcp' => 'SMALLINT',
|
|
|
- 'filter_group_id' => 'SMALLINT',
|
|
|
- 'dynamic' => 'SMALLINT',
|
|
|
- 'end_life' => 'TIMESTAMP',
|
|
|
- 'deleted' => 'SMALLINT',
|
|
|
- 'description' => 'VARCHAR(250)',
|
|
|
- 'dns_name' => 'VARCHAR(253)',
|
|
|
- 'dns_ptr_only' => 'SMALLINT',
|
|
|
- 'wikiname' => 'VARCHAR(250)',
|
|
|
- 'dhcp_acl' => 'TEXT',
|
|
|
- 'queue_id' => 'INTEGER',
|
|
|
- 'mac' => 'VARCHAR(20)',
|
|
|
- 'dhcp_action' => 'VARCHAR(10)',
|
|
|
- 'dhcp_option_set' => 'VARCHAR(50)',
|
|
|
- 'dhcp_time' => 'TIMESTAMP',
|
|
|
- 'dhcp_hostname' => 'VARCHAR(60)',
|
|
|
- 'last_found' => 'TIMESTAMP',
|
|
|
- 'arp_found' => 'TIMESTAMP',
|
|
|
- 'mac_found' => 'TIMESTAMP',
|
|
|
- 'blocked' => 'SMALLINT',
|
|
|
- 'day_quota' => 'INTEGER',
|
|
|
- 'month_quota' => 'INTEGER',
|
|
|
- 'device_model_id' => 'INTEGER',
|
|
|
- 'firmware' => 'VARCHAR(100)',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'client_id' => 'VARCHAR(250)',
|
|
|
- 'nagios' => 'SMALLINT',
|
|
|
- 'nagios_status' => 'VARCHAR(10)',
|
|
|
- 'nagios_handler' => 'VARCHAR(50)',
|
|
|
- 'link_check' => 'SMALLINT',
|
|
|
- 'changed' => 'SMALLINT',
|
|
|
- 'dhcp_changed' => 'SMALLINT',
|
|
|
- 'changed_time' => 'TIMESTAMP',
|
|
|
- 'created_by' => 'VARCHAR(10)',
|
|
|
- },
|
|
|
- 'user_auth_alias' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'auth_id' => 'INTEGER',
|
|
|
- 'alias' => 'VARCHAR(100)',
|
|
|
- 'description' => 'VARCHAR(100)',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'user_list' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'login' => 'VARCHAR(255)',
|
|
|
- 'description' => 'VARCHAR(255)',
|
|
|
- 'enabled' => 'SMALLINT',
|
|
|
- 'blocked' => 'SMALLINT',
|
|
|
- 'deleted' => 'SMALLINT',
|
|
|
- 'ou_id' => 'INTEGER',
|
|
|
- 'device_id' => 'INTEGER',
|
|
|
- 'filter_group_id' => 'INTEGER',
|
|
|
- 'queue_id' => 'INTEGER',
|
|
|
- 'day_quota' => 'INTEGER',
|
|
|
- 'month_quota' => 'INTEGER',
|
|
|
- 'permanent' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'user_sessions' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'session_id' => 'VARCHAR(128)',
|
|
|
- 'user_id' => 'INTEGER',
|
|
|
- 'ip_address' => 'VARCHAR(45)',
|
|
|
- 'user_agent' => 'TEXT',
|
|
|
- 'created_at' => 'INTEGER',
|
|
|
- 'last_activity' => 'INTEGER',
|
|
|
- 'is_active' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'user_stats' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'router_id' => 'BIGINT',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'byte_in' => 'BIGINT',
|
|
|
- 'byte_out' => 'BIGINT',
|
|
|
- 'pkt_in' => 'INTEGER',
|
|
|
- 'pkt_out' => 'INTEGER',
|
|
|
- 'step' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'user_stats_full' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'router_id' => 'BIGINT',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'byte_in' => 'BIGINT',
|
|
|
- 'byte_out' => 'BIGINT',
|
|
|
- 'pkt_in' => 'INTEGER',
|
|
|
- 'pkt_out' => 'INTEGER',
|
|
|
- 'step' => 'SMALLINT',
|
|
|
- },
|
|
|
- 'variables' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(30)',
|
|
|
- 'value' => 'VARCHAR(255)',
|
|
|
- 'clear_time' => 'TIMESTAMP',
|
|
|
- 'created' => 'TIMESTAMP',
|
|
|
- },
|
|
|
- 'vendors' => {
|
|
|
- 'id' => 'SERIAL',
|
|
|
- 'name' => 'VARCHAR(40)',
|
|
|
- },
|
|
|
- 'version' => {
|
|
|
- 'id' => 'INTEGER',
|
|
|
- 'version' => 'VARCHAR(10)',
|
|
|
- },
|
|
|
- 'wan_stats' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'router_id' => 'INTEGER',
|
|
|
- 'interface_id' => 'INTEGER',
|
|
|
- 'bytes_in' => 'BIGINT',
|
|
|
- 'bytes_out' => 'BIGINT',
|
|
|
- 'forward_in' => 'BIGINT',
|
|
|
- 'forward_out' => 'BIGINT',
|
|
|
- },
|
|
|
- 'worklog' => {
|
|
|
- 'id' => 'BIGSERIAL',
|
|
|
- 'ts' => 'TIMESTAMP',
|
|
|
- 'auth_id' => 'BIGINT',
|
|
|
- 'customer' => 'VARCHAR(50)',
|
|
|
- 'ip' => 'INET',
|
|
|
- 'message' => 'TEXT',
|
|
|
- 'level' => 'SMALLINT',
|
|
|
- },
|
|
|
-);
|
|
|
-
|
|
|
-sub get_table_columns {
|
|
|
- my ($db, $table) = @_;
|
|
|
- my $sth = $db->column_info(undef, undef, $table, '%');
|
|
|
- my @cols;
|
|
|
- while (my $row = $sth->fetchrow_hashref) {
|
|
|
- push @cols, $row->{COLUMN_NAME};
|
|
|
- }
|
|
|
- return @cols;
|
|
|
-}
|
|
|
-
|
|
|
sub batch_sql_cached {
|
|
|
my ($db, $sql, $data) = @_;
|
|
|
+ # Запоминаем исходное состояние 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() };
|
|
|
- print "batch_db_sql_cached failed: $err";
|
|
|
- return 0;
|
|
|
+ warn "batch_sql_cached failed: $err";
|
|
|
+ # Восстанавливаем AutoCommit даже при ошибке
|
|
|
+ $db->{AutoCommit} = $original_autocommit;
|
|
|
+ return 0;
|
|
|
};
|
|
|
+ # Восстанавливаем исходный режим AutoCommit
|
|
|
+ $db->{AutoCommit} = $original_autocommit;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
@@ -571,60 +133,62 @@ if ($opt_clear) {
|
|
|
|
|
|
print "\n=== Check DB schema ===\n\n";
|
|
|
|
|
|
-my $mysql_schema_status = 1;
|
|
|
-my %mysql_tables;
|
|
|
+# === Сбор полной схемы из обеих БД ===
|
|
|
+print "Fetching schema from MySQL and PostgreSQL...\n";
|
|
|
|
|
|
-# --- Этап 1: Проверяем, что все таблицы и колонки MySQL есть в PG-схеме ---
|
|
|
-for my $idx (0 .. $#tables_to_migrate) {
|
|
|
- my $table = $tables_to_migrate[$idx];
|
|
|
- my $table_num = $idx + 1;
|
|
|
+# === Сбор схем ===
|
|
|
+my %schema;
|
|
|
+for my $table (@tables_to_migrate) {
|
|
|
+ next if $table =~ /(traffic_detail|sessions)/i;
|
|
|
+ $schema{mysql}{$table} = { get_table_columns($mysql_db, $table) };
|
|
|
+}
|
|
|
|
|
|
- if ($table =~ /(traffic_detail|sessions)/) { next; }
|
|
|
+my @pg_tables = map { $_->{tablename} } get_records_sql($pg_db, "SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
|
|
|
+for my $table (@pg_tables) {
|
|
|
+ next if $table =~ /(traffic_detail|sessions)/i;
|
|
|
+ $schema{pg}{$table} = { get_table_columns($pg_db, $table) };
|
|
|
+}
|
|
|
|
|
|
- print "[$table_num/$total_tables] Processing table: $table\n";
|
|
|
+# === Флаг ошибки ===
|
|
|
+my $has_critical_error = 0;
|
|
|
|
|
|
- if (!exists $pg_schema{$table}) {
|
|
|
- print " ❗ WARNING: Table $table not found in Postgres DB schema! Will be skip for migration.\n";
|
|
|
- # Не считаем критичной ошибкой
|
|
|
+# === 1. Проверка: всё ли из PostgreSQL есть в MySQL? ===
|
|
|
+for my $table (keys %{ $schema{pg} }) {
|
|
|
+ if (!exists $schema{mysql}{$table}) {
|
|
|
+ print "❗ ERROR: Table '$table' exists in PostgreSQL but not in MySQL!\n";
|
|
|
+ $has_critical_error = 1;
|
|
|
next;
|
|
|
}
|
|
|
|
|
|
- my @columns = get_table_columns($mysql_db, $table);
|
|
|
- foreach my $column_name (@columns) {
|
|
|
- my $col_lower = lc($column_name); # Приводим к нижнему регистру
|
|
|
- if (!exists $pg_schema{$table}->{$col_lower}) {
|
|
|
- print " ❗ WARNING: Column $column_name in table $table not in PG schema. Will be skip for migration. \n";
|
|
|
- # Не считаем критичной ошибкой
|
|
|
- } else {
|
|
|
- $mysql_tables{$table}->{$col_lower} = 1;
|
|
|
+ for my $col (keys %{ $schema{pg}{$table} }) {
|
|
|
+ if (!exists $schema{mysql}{$table}{$col}) {
|
|
|
+ print "❗ ERROR: Column '$col' in table '$table' exists in PostgreSQL but not in MySQL!\n";
|
|
|
+ $has_critical_error = 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-# --- Этап 2: Проверяем, что все таблицы и колонки PG-схемы есть в MySQL ---
|
|
|
-for my $table (keys %pg_schema) {
|
|
|
-
|
|
|
- if ($table =~ /(traffic_detail|sessions)/) { next; }
|
|
|
-
|
|
|
- if (!exists $mysql_tables{$table}) {
|
|
|
- print " ❗ ERROR: Table $table from PG schema not found in source MySQL database!\n";
|
|
|
- $mysql_schema_status = 0;
|
|
|
+# === 2. Проверка: есть ли лишнее в MySQL? ===
|
|
|
+for my $table (keys %{ $schema{mysql} }) {
|
|
|
+ if (!exists $schema{pg}{$table}) {
|
|
|
+ print "⚠️ WARNING: Table '$table' exists in MySQL but not in PostgreSQL — will be skipped.\n";
|
|
|
next;
|
|
|
}
|
|
|
|
|
|
- for my $column_name (keys %{ $pg_schema{$table} }) {
|
|
|
- if (!exists $mysql_tables{$table}->{$column_name}) {
|
|
|
- print " ❗ ERROR: Column $column_name in table $table missing in MySQL!\n";
|
|
|
- $mysql_schema_status = 0;
|
|
|
+ for my $col (keys %{ $schema{mysql}{$table} }) {
|
|
|
+ if (!exists $schema{pg}{$table}{$col}) {
|
|
|
+ print "⚠️ WARNING: Column '$col' in table '$table' exists in MySQL but not in PostgreSQL — will be ignored.\n";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-if (!$mysql_schema_status) {
|
|
|
- print "\nSchema validation failed. Check database and try again.\n";
|
|
|
+if ($has_critical_error) {
|
|
|
+ print "\nSchema validation failed: missing required tables/columns in source MySQL database.\n";
|
|
|
exit 103;
|
|
|
}
|
|
|
|
|
|
+print "✅ Schema validation passed.\n\n";
|
|
|
+
|
|
|
print "\n=== Starting migration of $total_tables tables ===\n\n";
|
|
|
|
|
|
# === Миграция по таблицам с прогрессом ===
|
|
|
@@ -632,7 +196,7 @@ for my $idx (0 .. $#tables_to_migrate) {
|
|
|
my $table = $tables_to_migrate[$idx];
|
|
|
my $table_num = $idx + 1;
|
|
|
|
|
|
- if (!exists $pg_schema{$table}) { next; }
|
|
|
+ if (!exists $schema{pg}->{$table}) { next; }
|
|
|
|
|
|
print "[$table_num/$total_tables] Processing table: $table\n";
|
|
|
|
|
|
@@ -644,68 +208,113 @@ for my $idx (0 .. $#tables_to_migrate) {
|
|
|
next;
|
|
|
}
|
|
|
|
|
|
- # === Построчное чтение ===
|
|
|
- my $select_sth = $mysql_db->prepare("SELECT * FROM `$table`");
|
|
|
- $select_sth->execute();
|
|
|
|
|
|
my $inserted = 0;
|
|
|
my $errors = 0;
|
|
|
|
|
|
+ # === Построчное чтение ===
|
|
|
+ my $select_sth = $mysql_db->prepare("SELECT * FROM `$table`");
|
|
|
+ $select_sth->execute();
|
|
|
+
|
|
|
# === Режим вставки: построчный или пакетный ===
|
|
|
if ($opt_batch) {
|
|
|
print " → Using BATCH mode ($chunk_count records per chunk)\n";
|
|
|
|
|
|
- # Получаем список колонок один раз
|
|
|
- my @columns = get_table_columns($mysql_db, $table);
|
|
|
- my $quoted_columns = '"' . join('", "', @columns) . '"';
|
|
|
- my $placeholders = join(', ', ('?') x @columns);
|
|
|
- my $insert_sql = "INSERT INTO \"$table\" ($quoted_columns) VALUES ($placeholders)";
|
|
|
+ # Берём колонки напрямую из PostgreSQL-схемы — они все есть в MySQL
|
|
|
+ my @valid_columns = sort keys %{ $schema{pg}{$table} };
|
|
|
+
|
|
|
+ my $quoted_columns = '"' . join('", "', @valid_columns) . '"';
|
|
|
+ my $placeholders = join(', ', ('?') x @valid_columns);
|
|
|
+ my $insert_sql = "INSERT INTO \"$table\" ($quoted_columns) VALUES ($placeholders)";
|
|
|
|
|
|
my @batch_buffer;
|
|
|
my $chunk_size = $chunk_count;
|
|
|
|
|
|
+ my $processed = 0;
|
|
|
+ my $report_every = 10_000;
|
|
|
+
|
|
|
+
|
|
|
while (my $row = $select_sth->fetchrow_hashref) {
|
|
|
- my @values;
|
|
|
- for my $key (@columns) {
|
|
|
- if (!exists $pg_schema{$table}->{lc($key)}) { next; }
|
|
|
- my $value = $row->{$key};
|
|
|
- if (lc($key) eq 'ip') {
|
|
|
- $value = undef if !defined($value) || $value eq '';
|
|
|
- }
|
|
|
- push @values, $value;
|
|
|
- }
|
|
|
+ my @values;
|
|
|
+ for my $col (@valid_columns) {
|
|
|
+ my $raw_value = $row->{$col};
|
|
|
+ my $norm_value = normalize_value($raw_value, $schema{pg}{$table}{$col});
|
|
|
+ push @values, $norm_value;
|
|
|
+ }
|
|
|
push @batch_buffer, \@values;
|
|
|
+ $processed++;
|
|
|
+
|
|
|
if (@batch_buffer >= $chunk_count) {
|
|
|
- my $insert_status = batch_sql_cached($pg_db, $insert_sql, \@batch_buffer);
|
|
|
- if ($insert_status) { $inserted += @batch_buffer; } else { $errors+=@batch_buffer; }
|
|
|
+ my $insert_status = batch_sql_cached($pg_db, $insert_sql, \@batch_buffer);
|
|
|
+ if ($insert_status) {
|
|
|
+ $inserted += @batch_buffer;
|
|
|
+ } else {
|
|
|
+ $errors += @batch_buffer;
|
|
|
+ }
|
|
|
@batch_buffer = ();
|
|
|
- }
|
|
|
+
|
|
|
+ # Прогресс
|
|
|
+ if ($processed % $report_every == 0) {
|
|
|
+ my $pct = int($processed * 100 / $rec_count);
|
|
|
+ printf " → Processed: %d / %d (%d%%)\r", $processed, $rec_count, $pct;
|
|
|
+ $| = 1; # flush STDOUT
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
# Остаток
|
|
|
if (@batch_buffer) {
|
|
|
- my $insert_status = batch_sql_cached($pg_db, $insert_sql, \@batch_buffer);
|
|
|
- if ($insert_status) { $inserted += @batch_buffer; } else { $errors+=@batch_buffer; }
|
|
|
- }
|
|
|
+ my $insert_status = batch_sql_cached($pg_db, $insert_sql, \@batch_buffer);
|
|
|
+ if ($insert_status) {
|
|
|
+ $inserted += @batch_buffer;
|
|
|
+ } else {
|
|
|
+ $errors += @batch_buffer;
|
|
|
+ }
|
|
|
+ $processed += @batch_buffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Финальная строка
|
|
|
+ printf " → Processed: %d / %d (100%%)\n", $processed, $rec_count;
|
|
|
+
|
|
|
} else {
|
|
|
- # === построчный режим ===
|
|
|
+
|
|
|
+ # === Построчный режим ===
|
|
|
+ my $processed = 0;
|
|
|
+ my $report_every = 10_000;
|
|
|
+
|
|
|
while (my $row = $select_sth->fetchrow_hashref) {
|
|
|
- # === Приведение имён полей к нижнему регистру ===
|
|
|
my %row_normalized;
|
|
|
- while (my ($key, $value) = each %$row) {
|
|
|
- my $n_key = lc($key);
|
|
|
- if ($n_key eq 'ip') {
|
|
|
- if (!defined $value || $value eq '') { $value = undef; }
|
|
|
- }
|
|
|
- $row_normalized{$n_key} = $value;
|
|
|
+ for my $col (keys %{ $schema{pg}{$table} }) {
|
|
|
+ my $raw_value = $row->{$col};
|
|
|
+ my $norm_value = normalize_value($raw_value, $schema{pg}{$table}{$col});
|
|
|
+ $row_normalized{$col} = $norm_value;
|
|
|
}
|
|
|
|
|
|
my $ret_id = insert_record($pg_db, $table, \%row_normalized);
|
|
|
- if ($ret_id>0) { $inserted++; } else {
|
|
|
+ if ($ret_id > 0) {
|
|
|
+ $inserted++;
|
|
|
+ } else {
|
|
|
$errors++;
|
|
|
- print Dumper(\%row_normalized) if ($debug);
|
|
|
- }
|
|
|
+ print Dumper(\%row_normalized) if ($debug);
|
|
|
+ }
|
|
|
+
|
|
|
+ $processed++;
|
|
|
+
|
|
|
+ # Прогресс каждые N строк
|
|
|
+ if ($rec_count > 0 && $processed % $report_every == 0) {
|
|
|
+ my $pct = int($processed * 100 / $rec_count);
|
|
|
+ printf " → Processed: %d / %d (%d%%)\r", $processed, $rec_count, $pct;
|
|
|
+ $| = 1; # flush
|
|
|
+ }
|
|
|
}
|
|
|
$select_sth->finish();
|
|
|
+
|
|
|
+ # Финальная строка
|
|
|
+ if ($rec_count > 0) {
|
|
|
+ printf " → Processed: %d / %d (100%%)\n", $processed, $rec_count;
|
|
|
+ } else {
|
|
|
+ print " → Processed: $processed records\n";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
# === Итог по таблице ===
|
|
|
@@ -720,5 +329,32 @@ if ($opt_batch) {
|
|
|
print "\n";
|
|
|
}
|
|
|
|
|
|
+print "\n=== Resetting all table sequences ===\n";
|
|
|
+
|
|
|
+# Получаем список всех таблиц из целевой схемы (PostgreSQL)
|
|
|
+my $tables_sql = "SELECT tablename FROM pg_tables WHERE schemaname = 'public'";
|
|
|
+my $sth = $pg_db->prepare($tables_sql);
|
|
|
+$sth->execute();
|
|
|
+
|
|
|
+while (my ($table) = $sth->fetchrow_array) {
|
|
|
+ # Формируем имя последовательности
|
|
|
+ my $seq_name = "${table}_id_seq";
|
|
|
+ # Проверяем, существует ли такая последовательность
|
|
|
+ my ($exists) = $pg_db->selectrow_array(
|
|
|
+ "SELECT 1 FROM pg_class WHERE relname = ? AND relkind = 'S'",
|
|
|
+ undef, $seq_name
|
|
|
+ );
|
|
|
+ if ($exists) {
|
|
|
+ # Получаем MAX(id)
|
|
|
+ my ($max_id) = $pg_db->selectrow_array("SELECT MAX(id) FROM \"$table\"");
|
|
|
+ $max_id //= 1;
|
|
|
+ # Сбрасываем последовательность
|
|
|
+ $pg_db->do("SELECT setval('$seq_name', $max_id)");
|
|
|
+ print " → $table: sequence reset to $max_id\n";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+print "✅ All sequences updated.\n";
|
|
|
+
|
|
|
print "🎉 Migration completed! Processed $total_tables tables.\n";
|
|
|
exit 0;
|