#!/usr/bin/env perl # # Zabbix # Copyright (C) 2001-2023 Zabbix SIA # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation # # 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. use strict; use warnings; use File::Basename; my $file = dirname($0) . "/../src/schema.tmpl"; # name the file my ($state, %output, $eol, $fk_bol, $fk_eol, $ltab, $pkey, $table_name, $pkey_name); my ($szcol1, $szcol2, $szcol3, $szcol4, $sequences, $sql_suffix, $triggers); my ($fkeys, $fkeys_prefix, $fkeys_suffix, $uniq, $delete_cascade); my %table_types; # for making sure that table types aren't duplicated my %c = ( "type" => "code", "database" => "", "after" => "\t{0}\n};\n\n#undef ZBX_TYPE_LONGTEXT_LEN\n#undef ZBX_TYPE_SHORTTEXT_LEN\n", "t_bigint" => "ZBX_TYPE_UINT", "t_text" => "ZBX_TYPE_TEXT", "t_double" => "ZBX_TYPE_FLOAT", "t_id" => "ZBX_TYPE_ID", "t_image" => "ZBX_TYPE_BLOB", "t_bin" => "ZBX_TYPE_BLOB", "t_integer" => "ZBX_TYPE_INT", "t_longtext" => "ZBX_TYPE_LONGTEXT", "t_nanosec" => "ZBX_TYPE_INT", "t_serial" => "ZBX_TYPE_UINT", "t_shorttext" => "ZBX_TYPE_SHORTTEXT", "t_time" => "ZBX_TYPE_INT", "t_varchar" => "ZBX_TYPE_CHAR", "t_cuid" => "ZBX_TYPE_CUID", ); $c{"before"} = "/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include \"zbxdbschema.h\" #include \"zbxcommon.h\" #if defined(HAVE_ORACLE) # define ZBX_TYPE_SHORTTEXT_LEN 2048 #else # define ZBX_TYPE_SHORTTEXT_LEN 65535 #endif #define ZBX_TYPE_LONGTEXT_LEN 0 #define ZBX_TYPE_TEXT_LEN 65535 static zbx_db_table_t\ttables[] = { "; my %mysql = ( "type" => "sql", "database" => "mysql", "before" => "", "after" => "", "table_options" => " ENGINE=InnoDB", "t_bigint" => "bigint unsigned", "t_text" => "text", "t_double" => "DOUBLE PRECISION", "t_id" => "bigint unsigned", "t_image" => "longblob", "t_bin" => "longblob", "t_integer" => "integer", "t_longtext" => "longtext", "t_nanosec" => "integer", "t_serial" => "bigint unsigned", "t_shorttext" => "text", "t_time" => "integer", "t_varchar" => "varchar", "t_cuid" => "varchar(25)", ); my %oracle = ( "type" => "sql", "database" => "oracle", "before" => "", "after" => "", "table_options" => "", "t_bigint" => "number(20)", "t_text" => "nclob", "t_double" => "BINARY_DOUBLE", "t_id" => "number(20)", "t_image" => "blob", "t_bin" => "blob", "t_integer" => "number(10)", "t_longtext" => "nclob", "t_nanosec" => "number(10)", "t_serial" => "number(20)", "t_shorttext" => "nvarchar2(2048)", "t_time" => "number(10)", "t_varchar" => "nvarchar2", "t_cuid" => "nvarchar2(25)", ); my %postgresql = ( "type" => "sql", "database" => "postgresql", "before" => "", "after" => "", "table_options" => "", "t_bigint" => "numeric(20)", "t_text" => "text", "t_double" => "DOUBLE PRECISION", "t_id" => "bigint", "t_image" => "bytea", "t_bin" => "bytea", "t_integer" => "integer", "t_longtext" => "text", "t_nanosec" => "integer", "t_serial" => "bigserial", "t_shorttext" => "text", "t_time" => "integer", "t_varchar" => "varchar", "t_cuid" => "varchar(25)", ); my %sqlite3 = ( "type" => "sql", "database" => "sqlite3", "before" => "", "after" => "", "table_options" => "", "t_bigint" => "bigint", "t_text" => "text", "t_double" => "DOUBLE PRECISION", "t_id" => "bigint", "t_image" => "longblob", "t_bin" => "longblob", "t_integer" => "integer", "t_longtext" => "text", "t_nanosec" => "integer", "t_serial" => "integer", "t_shorttext" => "text", "t_time" => "integer", "t_varchar" => "varchar", "t_cuid" => "varchar(25)", ); sub rtrim($) { my $string = shift; $string =~ s/(\r|\n)+$// if ($string); return $string; } sub newstate($) { my $new = shift; if ($state eq "field") { if ($output{"type"} eq "sql" && ($new eq "index" || $new eq "table" || $new eq "row")) { print "${pkey}${eol}\n)$output{'table_options'};${eol}\n"; } if ($new eq "field") { print ",${eol}\n"; } } if ($state ne "bof") { if ($output{"type"} eq "code" && $new eq "table") { if ($uniq ne "") { print ",\n\t\t\t{0}\n\t\t}${uniq}\n\t},\n"; $uniq = ""; } else { print ",\n\t\t\t{0}\n\t\t},\n\t\tNULL\n\t},\n"; } } } $state = $new; } sub process_table($) { my $line = shift; my $flags; newstate("table"); $delete_cascade = 0; ($table_name, $pkey_name, $flags) = split(/\|/, $line, 3); if ($output{"type"} eq "code") { if ($flags eq "") { $flags = "0"; } for ($flags) { # do not output ZBX_DATA ZBX_DASHBOARD and ZBX_TEMPLATE, remove it s/ZBX_DATA//; s/ZBX_TEMPLATE//; s/ZBX_DASHBOARD//; s/,+$//; s/^,+//; s/,+/ \| /g; s/^$/0/; } print "\t{\"${table_name}\", \"${pkey_name}\", ${flags},\n\t\t{\n"; } else { if ($pkey_name ne "") { $pkey = ",${eol}\n${ltab}PRIMARY KEY (${pkey_name})"; } else { $pkey = ""; } if ($output{"database"} eq "mysql") { print "CREATE TABLE `${table_name}` (${eol}\n"; } else { print "CREATE TABLE ${table_name} (${eol}\n"; } } } sub process_field($) { my $line = shift; newstate("field"); my ($name, $type, $default, $null, $flags, $relN, $fk_table, $fk_field, $fk_flags) = split(/\|/, $line, 9); my ($type_short, $length) = $type =~ /^(\w+)(?:\((\d+)\))?$/; if ($output{"type"} eq "code") { $type = $output{$type_short}; if ($type eq "ZBX_TYPE_CHAR") { # use specified $length, don't override it } elsif ($type eq "ZBX_TYPE_TEXT") { $length = "ZBX_TYPE_TEXT_LEN"; } elsif ($type eq "ZBX_TYPE_SHORTTEXT") { $length = "ZBX_TYPE_SHORTTEXT_LEN"; } elsif ($type eq "ZBX_TYPE_LONGTEXT") { $length = "ZBX_TYPE_LONGTEXT_LEN"; } elsif ($type eq "ZBX_TYPE_CUID") { $length = 0; } else { $length = 0; } for ($flags) { # do not output ZBX_NODATA, remove it s/ZBX_NODATA//; s/,+$//; s/^,+//; s/,+/ \| /g; s/^$/0/; } if ($null eq "NOT NULL") { if ($flags ne "0") { $flags = "ZBX_NOTNULL | ${flags}"; } else { $flags = "ZBX_NOTNULL"; } } $flags =~ s/,/ \| /g; if ($fk_table) { if (not $fk_field or $fk_field eq "") { $fk_field = $name; } $fk_table = "\"${fk_table}\""; $fk_field = "\"${fk_field}\""; if (not $fk_flags or $fk_flags eq "") { $delete_cascade = 1; $fk_flags = "ZBX_FK_CASCADE_DELETE"; } elsif ($fk_flags eq "RESTRICT") { $fk_flags = "0"; } } else { $fk_table = "NULL"; $fk_field = "NULL"; $fk_flags = "0"; } if ($default eq "") { $default = "NULL"; } else { $default =~ s/'//g; $default = "\"$default\""; } print "\t\t\t{\"${name}\", ${default}, ${fk_table}, ${fk_field}, ${length}, $type, ${flags}, ${fk_flags}}"; } else { my @text_fields; $type =~ s/$type_short/$output{$type_short}/g; if (($output{"database"} eq "oracle") && (index($type, "nvarchar2") == 0 || index($type, "nclob") == 0)) { $null = ""; } my $row = $null; if ($type_short eq "t_serial") { if ($output{"database"} eq "sqlite3") { $row = sprintf("%-*s PRIMARY KEY AUTOINCREMENT", $szcol4, $row); $pkey = ""; } elsif ($output{"database"} eq "mysql") { $row = sprintf("%-*s auto_increment", $szcol4, $row); } elsif ($output{"database"} eq "oracle") { $sequences .= "CREATE SEQUENCE ${table_name}_seq${eol}\n"; $sequences .= "START WITH 1${eol}\n"; $sequences .= "INCREMENT BY 1${eol}\n"; $sequences .= "NOMAXVALUE${eol}\n/${eol}\n"; $sequences .= "CREATE TRIGGER ${table_name}_tr${eol}\n"; $sequences .= "BEFORE INSERT ON ${table_name}${eol}\n"; $sequences .= "FOR EACH ROW${eol}\n"; $sequences .= "BEGIN${eol}\n"; $sequences .= "SELECT ${table_name}_seq.nextval INTO :new.${name} FROM dual;${eol}\n"; $sequences .= "END;${eol}\n/${eol}\n"; } } my $references = ""; if ($relN and $relN ne "" and $relN ne "-") { my $only = ""; if (not $fk_field or $fk_field eq "") { $fk_field = $name; } # RESTRICT may contain new line chars, we need to clean them out $fk_flags = rtrim($fk_flags); if (not $fk_flags or $fk_flags eq "") { $delete_cascade = 1; $fk_flags = " ON DELETE CASCADE"; } elsif ($fk_flags eq "RESTRICT") { $fk_flags = ""; } if ($output{"database"} eq "postgresql") { $only = " ONLY"; } my $cname = "c_${table_name}_${relN}"; if ($output{"database"} eq "sqlite3") { $references = " REFERENCES ${fk_table} (${fk_field})${fk_flags}"; } else { $references = ""; if ($output{"database"} eq "mysql") { $fkeys .= "${fk_bol}ALTER TABLE${only} `${table_name}` ADD CONSTRAINT `${cname}` FOREIGN KEY (`${name}`) REFERENCES `${fk_table}` (`${fk_field}`)${fk_flags}${fk_eol}\n"; } else { $fkeys .= "${fk_bol}ALTER TABLE${only} ${table_name} ADD CONSTRAINT ${cname} FOREIGN KEY (${name}) REFERENCES ${fk_table} (${fk_field})${fk_flags}${fk_eol}\n"; } } } if ($output{"database"} eq "mysql") { @text_fields = ('blob', 'longblob', 'text', 'longtext'); $default = "" if (grep /$output{$type_short}/, @text_fields); $name = "`${name}`"; } if ($default ne "") { $default = "DEFAULT $default"; } printf("${ltab}%-*s %-*s %-*s ${row}${references}", $szcol1, $name, $szcol2, $type, $szcol3, $default); } } sub process_index($$) { my $line = shift; my $unique = shift; newstate("index"); my ($name, $fields) = split(/\|/, $line, 2); if ($output{"type"} eq "code") { if (1 == $unique) { $uniq = ",\n\t\t\"${fields}\""; } } else { if (1 == $unique) { $unique = " UNIQUE"; } else { $unique = ""; } if ($output{"database"} eq "mysql") { $fields =~ s/,/`,`/g; my $quote_index = "`$fields`"; $quote_index =~ s/\)`/\)/g; $quote_index =~ s/\(/`\(/g; print "CREATE${unique} INDEX `${table_name}_$name` ON `$table_name` ($quote_index);${eol}\n"; } else { $fields =~ s/\(\d+\)//g; print "CREATE${unique} INDEX ${table_name}_$name ON $table_name ($fields);${eol}\n"; } } } sub process_row($) { my $line = shift; newstate("row"); my @array = split(/\|/, $line); my $first = 1; my $values = "("; foreach (@array) { $values .= "," if ($first == 0); $first = 0; # remove leading and trailing spaces $_ =~ s/^\s+//; $_ =~ s/\s+$//; if ($_ eq 'NULL') { $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; if ($output{'database'} eq 'mysql' || $output{'database'} eq 'oracle') { $_ =~ s/&eol;/\\r\\n/g; } else { $_ =~ s/&eol;/\x0D\x0A/g; } $values .= "${modifier}'${_}'"; } } $values .= ")"; print "INSERT INTO $table_name VALUES $values;${eol}\n"; } sub timescaledb() { print< 86400, $flags); EOF ; } print< 604800, time_partitioning_func => 'cuid_timestamp', $flags); EOF ; for ("trends", "trends_uint") { print< 2592000, $flags); EOF ; } print<; # read it into an array close(INFO); # close the file foreach my $line (@lines) { $line =~ tr/\t//d; chop($line); my ($type, $opts) = split(/\|/, $line, 2); if ($type) { if ($type eq 'FIELD') { process_field($opts); } elsif ($type eq 'INDEX') { process_index($opts, 0); } elsif ($type eq 'TABLE') { process_table($opts); } elsif ($type eq 'UNIQUE') { process_index($opts, 1); } elsif ($type eq 'CHANGELOG') { process_changelog($opts); } elsif ($type eq 'UPD_TRIG_FUNC') { process_update_trigger_function($opts); } elsif ($type eq 'ROW' && $output{"type"} ne "code") { process_row($opts); } } } newstate("table"); if ($output{"database"} eq "mysql") { print "DELIMITER \$\$${eol}\n"; } print $sequences . $triggers . $sql_suffix; if ($output{"database"} eq "mysql") { print "DELIMITER ;${eol}\n"; } print $fkeys_prefix . $fkeys . $fkeys_suffix; print $output{"after"}; } sub c_append_changelog_tables() { print " static const zbx_db_table_changelog_t\tchangelog_tables[] = {\n"; while (my ($object, $table) = each(%table_types)) { print "\t{\"$table\", $object},\n" } print "\t{0}\n};\n"; } sub main() { if ($#ARGV != 0) { usage(); } $eol = ""; $fk_bol = ""; $fk_eol = ";"; $ltab = "\t"; $szcol1 = 24; $szcol2 = 15; $szcol3 = 25; $szcol4 = 7; $sql_suffix = ""; $fkeys_prefix = ""; $fkeys_suffix = ""; my $format = $ARGV[0]; if ($format eq 'c') { %output = %c; } elsif ($format eq 'mysql') { %output = %mysql; } elsif ($format eq 'oracle') { %output = %oracle; } elsif ($format eq 'postgresql') { %output = %postgresql; } elsif ($format eq 'sqlite3') { %output = %sqlite3; } elsif ($format eq 'timescaledb') { timescaledb(); } else { usage(); } process(); if ($format eq "c") { c_append_changelog_tables(); $eol = "\\n\\"; $fk_bol = "\t\""; $fk_eol = "\","; $ltab = ""; $szcol1 = 0; $szcol2 = 0; $szcol3 = 0; $szcol4 = 0; $sql_suffix="\";\n"; print "#if defined(HAVE_SQLITE3)\nstatic const char\t*db_schema = \"\\\n"; %output = %sqlite3; process(); print "#else\t/* HAVE_SQLITE3 */\n"; print "static const char\t*db_schema = NULL;\n"; print "#endif\t/* not HAVE_SQLITE3 */\n"; print "\nzbx_db_table_t\t*zbx_dbschema_get_tables(void)\n{\n\treturn tables;\n}\n"; print "\nconst zbx_db_table_changelog_t\t*zbx_dbschema_get_changelog_tables(void)\n" . "{\n\treturn changelog_tables;\n}\n"; print "\nconst char\t*zbx_dbschema_get_schema(void)\n{\n\treturn db_schema;\n}\n"; } } main();