#!/usr/bin/env perl # # Copyright (C) 2001-2025 Zabbix SIA # # This program is free software: you can redistribute it and/or modify it under the terms of # the GNU Affero General Public License as published by the Free Software Foundation, version 3. # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License along with this program. # If not, see <https://www.gnu.org/licenses/>. use strict; use File::Basename; my (%output, $insert_into, $fields, @values_list); my %mysql = ( "database" => "mysql", "before" => "START TRANSACTION;\n", "after" => "COMMIT;\n", "exec_cmd" => ";\n" ); my %oracle = ( "database" => "oracle", "before" => "SET DEFINE OFF\n", "after" => "", "exec_cmd" => "\n/\n\n" ); my %postgresql = ( "database" => "postgresql", "before" => "START TRANSACTION;\n", "after" => "COMMIT;\n", "exec_cmd" => ";\n" ); # Maximum line length that SQL*Plus can read from .sql file is 2499 characters. # Splitting long entries in 'media_type' table have to happen before SQL*Plus limit has been reached and end-of-line # character has to stay intact in one line. my $oracle_field_limit = 2048; sub process_table { my $line = $_[0]; $line = "`$line`" if ($output{'database'} eq 'mysql'); $insert_into = "INSERT INTO $line"; @values_list = (); # Reset the values list when processing a new table } sub process_fields { my $line = $_[0]; my @array = split(/\|/, $line); my $first = 1; $fields = "("; if ($output{'database'} eq 'mysql') { foreach (@array) { $fields = "$fields," if ($first == 0); $first = 0; $_ =~ s/\s+$//; # remove trailing spaces $fields = "$fields`$_`"; } } else { foreach (@array) { $fields = "$fields," if ($first == 0); $first = 0; $_ =~ s/\s+$//; # remove trailing spaces $fields = "$fields$_"; } } $fields = "$fields)"; } sub process_row { my $line = $_[0]; my @array = split(/\|/, $line); my $first = 1; my $values = "("; my $split_script_field = 0; foreach (@array) { $values = "$values," if ($first == 0); $first = 0; # remove leading and trailing spaces $_ =~ s/^\s+//; $_ =~ s/\s+$//; if ($_ eq 'NULL') { $values = "$values$_"; } else { my $modifier = ''; # escape backslashes if (/\\/) { if ($output{'database'} eq 'postgresql') { $_ =~ s/\\/\\\\/g; $modifier = 'E'; } elsif ($output{'database'} eq 'mysql') { $_ =~ s/\\/\\\\/g; } } # escape single quotes if (/'/) { if ($output{'database'} eq 'mysql') { $_ =~ s/'/\\'/g; } else { $_ =~ s/'/''/g; } } $_ =~ s/&pipe;/|/g; $_ =~ s/&tab;/\t/g; if ($output{'database'} eq 'mysql') { $_ =~ s/&eol;/\\r\\n/g; $_ =~ s/&bsn;/\\n/g; } elsif ($output{'database'} eq 'oracle') { $_ =~ s/&eol;/' || chr(13) || chr(10) || '/g; $_ =~ s/&bsn;/' || chr(10) || '/g; if (length($_) > $oracle_field_limit) { my @sections = unpack("(a$oracle_field_limit)*", $_); my $move_to_next; my $first_part = 1; my $script; $split_script_field = 1; foreach (@sections) { # split after 'end of line' character and move what is left to the next line if (/(.*' \|\| (?:chr\(13\) \|\| )?chr\(10\) \|\| ')(.*)/) { if ($first_part == 1) { $script = "TO_NCLOB('$1')"; $first_part = 0; } else { $script = "${script}||\nTO_NCLOB('$move_to_next$1')"; } $move_to_next = $2; } else { $move_to_next = "$move_to_next$_"; } } if (length($move_to_next) > 0) { if (length($script) + length($move_to_next) < $oracle_field_limit) { substr($script, length($script) - 2, 2, "$move_to_next')"); } else { substr($script, length($script), 0, "||\nTO_NCLOB('$move_to_next')"); } } $_ = $script; } } else { $_ =~ s/&eol;/\x0D\x0A/g; $_ =~ s/&bsn;/\x0A/g; } # can be set to 1 only if Oracle DB is used if ($split_script_field == 1) { $values = "$values$modifier$_"; $split_script_field = 0; } else { $values = "$values$modifier'$_'"; } } } $values = "$values)"; # Add the current row's values to the list push @values_list, $values; } sub flush_bulk_insert { if (@values_list) { my $bulk_insert = $output{'database'} eq 'mysql' || $output{'database'} eq 'postgresql' ? "$insert_into $fields VALUES ".join(",\n", @values_list).$output{'exec_cmd'} : "$insert_into $fields\nvalues ".join("\n/\n\n$insert_into $fields\nvalues ", @values_list).$output{'exec_cmd'}; print $bulk_insert; @values_list = (); # Clear the values list after printing } } sub usage { print "Usage: $0 [mysql|oracle|postgresql]\n"; print "The script generates Zabbix SQL data files for different database engines.\n"; exit; } sub main { if ($#ARGV != 0) { usage(); } if ($ARGV[0] eq 'mysql') { %output = %mysql; } elsif ($ARGV[0] eq 'oracle') { %output = %oracle; } elsif ($ARGV[0] eq 'postgresql') { %output = %postgresql; } else { usage(); } print $output{"before"}; my ($line, $type); foreach $line ( <STDIN> ) { $line =~ tr/\t//d; chop($line); ($type, $line) = split(/\|/, $line, 2); if ($type) { $type =~ s/\s+$//; # remove trailing spaces if ($type eq 'FIELDS') { process_fields($line); } elsif ($type eq 'TABLE') { flush_bulk_insert(); process_table($line); } elsif ($type eq 'ROW') { process_row($line); } } } flush_bulk_insert(); # Ensure the last batch of rows is printed print "DELETE FROM changelog$output{'exec_cmd'}"; print $output{"after"}; } main();