eqemu-server/utils/scripts/generators/repository-generator.pl
2020-04-04 02:39:15 -05:00

407 lines
13 KiB
Perl

#!/usr/bin/perl
# Author: Akkadius
# @file: repository-generator.pl
# @description: Script used to generate database repositories
# @example perl ~/code/utils/scripts/generators/repository-generator.pl ~/server/
#############################################
# modules
#############################################
use warnings FATAL => 'all';
no warnings 'experimental::smartmatch';
use experimental 'smartmatch';
use File::Find;
use Data::Dumper;
use DBI;
use DBD::mysql;
use JSON;
my $json = new JSON();
#############################################
# args
#############################################
my $server_path = $ARGV[0];
my $config_path = $server_path . "/eqemu_config.json";
my $requested_table_to_generate = $ARGV[1] ? $ARGV[1] : "";
#############################################
# world path
#############################################
my $world_path = $server_path . "/world";
my $world_path_bin = $server_path . "/bin/world";
my $found_world_path = "";
if (-e $world_path) {
$found_world_path = $world_path;
}
elsif (-e $world_path_bin) {
$found_world_path = $world_path_bin;
}
if ($found_world_path eq "") {
print "Error! Cannot find world binary!\n";
exit;
}
#############################################
# validate config
#############################################
if (!-e $config_path) {
print "Error! Config file [$config_path] not found\n";
exit;
}
#############################################
# fetch schema from world
#############################################
my $output = `cd $server_path && $found_world_path database:schema`;
my $database_schema = $json->decode($output);
#############################################
# database
#############################################
my $content;
open(my $fh, '<', $config_path) or die "cannot open file $config_path"; {
local $/;
$content = <$fh>;
}
close($fh);
#############################################
# database
#############################################
my $config = $json->decode($content);
my $database_name = $config->{"server"}{"database"}{"db"};
my $host = $config->{"server"}{"database"}{"host"};
my $user = $config->{"server"}{"database"}{"username"};
my $pass = $config->{"server"}{"database"}{"password"};
my $dsn = "dbi:mysql:$database_name:$host:3306";
my $connect = DBI->connect($dsn, $user, $pass);
my @tables = ($requested_table_to_generate);
if ($requested_table_to_generate eq "all" || !$requested_table_to_generate) {
my $table_names_exec = $connect->prepare(
"
SELECT
TABLE_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = ?
GROUP BY
TABLE_NAME
");
$table_names_exec->execute($database_name);
while (my @row = $table_names_exec->fetchrow_array()) {
push(@tables, $row[0]);
}
}
my $generated_repository_files = "";
foreach my $table_to_generate (@tables) {
my $table_found_in_schema = 0;
my @categories = (
"content_tables",
"version_tables",
"state_tables",
"server_tables",
"player_tables",
"login_tables",
);
# These tables don't have a typical schema
my @table_ignore_list = (
"character_enabledtasks",
"grid", # Manually created
"grid_entries", # Manually created
"tradeskill_recipe", # Manually created
"character_recipe_list", # Manually created
"guild_bank",
"inventory_versions",
"raid_leaders",
"sharedbank",
"trader_audit",
"eqtime",
"db_version",
"keyring",
"profanity_list",
"zone_flags",
);
foreach my $category (@categories) {
if ($table_to_generate ~~ $database_schema->{$category}) {
$table_found_in_schema = 1;
}
}
if ($table_to_generate ~~ @table_ignore_list) {
print "Table [$table_to_generate] is on ignore list... skipping...\n";
$table_found_in_schema = 0;
}
if ($table_found_in_schema == 0) {
print "Table [$table_to_generate] not found in schema, skipping\n";
next;
}
my $ex = $connect->prepare(
"
SELECT
COLUMN_NAME,
TABLE_NAME,
DATA_TYPE,
COLUMN_TYPE,
ORDINAL_POSITION,
COLUMN_KEY,
COLUMN_DEFAULT
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_SCHEMA = ?
AND TABLE_NAME = ?
ORDER BY TABLE_NAME, ORDINAL_POSITION
"
);
$ex->execute($database_name, $table_to_generate);
my $longest_column_length = 0;
my $longest_data_type_length = 0;
while (my @row = $ex->fetchrow_array()) {
my $column_name = $row[0];
my $data_type = $row[2];
if ($longest_column_length < length($column_name)) {
$longest_column_length = length($column_name);
}
my $struct_data_type = translate_mysql_data_type_to_c($data_type);
if ($longest_data_type_length < length($struct_data_type)) {
$longest_data_type_length = length($struct_data_type);
}
}
# 2nd pass
my $default_entries = "";
my $insert_one_entries = "";
my $insert_many_entries = "";
my $find_one_entries = "";
my $column_names_quoted = "";
my $table_struct_columns = "";
my $update_one_entries = "";
my $all_entries = "";
my $index = 0;
my %table_data = ();
my %table_primary_key = ();
$ex->execute($database_name, $table_to_generate);
$table_primary_key{$table_to_generate} = "id";
while (my @row = $ex->fetchrow_array()) {
my $column_name = $row[0];
my $table_name = $row[1];
my $data_type = $row[2];
my $column_type = $row[3];
my $ordinal_position = $row[4];
my $column_key = $row[5];
my $column_default = ($row[6] ? $row[6] : "");
if ($column_key eq "PRI" || ($ordinal_position == 0 && $column_name=~/id/i)) {
$table_primary_key{$table_name} = $column_name;
}
my $default_value = 0;
if ($column_default ne "NULL" && $column_default ne "") {
$default_value = $column_default;
}
if ($column_default eq "''") {
$default_value = '""';
}
if (trim($column_default) eq "" && $column_type =~ /text|varchar/i) {
$default_value = '""';
}
my $struct_data_type = translate_mysql_data_type_to_c($data_type);
# struct
$table_struct_columns .= sprintf("\t\t\%-${longest_data_type_length}s %s;\n", $struct_data_type, $column_name);
# new entity
$default_entries .= sprintf("\t\tentry.%-${longest_column_length}s = %s;\n", $column_name, $default_value);
# column names (string)
$column_names_quoted .= sprintf("\t\t\t\"%s\",\n", $column_name);
# update one
if ($column_key ne "PRI") {
my $query_value = sprintf('\'" + EscapeString(%s_entry.%s) + "\'");', $table_name, $column_name);
if ($data_type =~ /int/) {
$query_value = sprintf('" + std::to_string(%s_entry.%s));', $table_name, $column_name);
}
$update_one_entries .= sprintf(
"\t\t" . 'update_values.push_back(columns[%s] + " = %s' . "\n",
$index,
$query_value
);
}
# insert one
if ($column_key ne "PRI") {
my $value = sprintf("\"'\" + EscapeString(%s_entry.%s) + \"'\"", $table_name, $column_name);
if ($data_type =~ /int/) {
$value = sprintf('std::to_string(%s_entry.%s)', $table_name, $column_name);
}
$insert_one_entries .= sprintf("\t\tinsert_values.push_back(%s);\n", $value);
$insert_many_entries .= sprintf("\t\t\tinsert_values.push_back(%s);\n", $value);
}
# find one / all (select)
if ($data_type =~ /int/) {
$all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name, $index);
$find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atoi(row[%s]);\n", $column_name, $index);
}
elsif ($data_type =~ /float|double|decimal/) {
$all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atof(row[%s]);\n", $column_name, $index);
$find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = atof(row[%s]);\n", $column_name, $index);
}
else {
$all_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s];\n", $column_name, $index);
$find_one_entries .= sprintf("\t\t\tentry.%-${longest_column_length}s = row[%s];\n", $column_name, $index);
}
# print $column_name . "\n";
# print "table_name [$table_name] column_name [$column_name] data_type [$data_type] column_type [$column_type]\n";
$index++;
}
#############################################
# repository template
#############################################
my $repository_template_file = './common/repositories/template/repository.template';
my $repository_template = "";
if (-e $repository_template_file) {
open(my $fh, '<:encoding(UTF-8)', $repository_template_file) or die "Could not open file '$repository_template_file' $!";
while (my $line = <$fh>) {
$repository_template .= $line;
}
close $fh;
}
if (trim($repository_template) eq "") {
print "Repository template not found! [$repository_template_file]\n";
exit;
}
foreach my $column (keys %{$table_data{$table_to_generate}}) {
my $column_data = $table_data{$table_to_generate}{$column};
my $data_type = $column_data->[0];
my $column_type = $column_data->[1];
my $ordinal_position = $column_data->[2];
my $column_default = $column_data->[3];
# print "Column [$column] data_type [$data_type] column_type [$column_type] ordinal [$ordinal_position]\n";
}
my $table_name_camel_case = $table_to_generate;
my $table_name_upper_case = uc($table_to_generate);
$table_name_camel_case =~ s#(_|^)(.)#\u$2#g;
my $primary_key = ($table_primary_key{$table_to_generate} ? $table_primary_key{$table_to_generate} : "");
my $database_connection = "database";
if ($table_to_generate ~~ $database_schema->{"content_tables"}) {
$database_connection = "content_db";
}
chomp($column_names_quoted);
chomp($table_struct_columns);
chomp($default_entries);
chomp($update_one_entries);
chomp($insert_one_entries);
chomp($insert_many_entries);
chomp($all_entries);
print "Table name CamelCase [$table_name_camel_case]\n";
print "Table name UPPER_CASE [$table_name_upper_case]\n";
print "Table PRIMARY KEY [$primary_key]\n";
print "Database connection [$database_connection]\n";
my $new_repository = $repository_template;
$new_repository =~ s/\{\{TABLE_NAME_CLASS}}/$table_name_camel_case/g;
$new_repository =~ s/\{\{TABLE_NAME_UPPER}}/$table_name_upper_case/g;
$new_repository =~ s/\{\{PRIMARY_KEY_STRING}}/$primary_key/g;
$new_repository =~ s/\{\{TABLE_NAME_STRUCT}}/$table_name_camel_case/g;
$new_repository =~ s/\{\{TABLE_NAME_VAR}}/$table_to_generate/g;
$new_repository =~ s/\{\{DATABASE_CONNECTION}}/$database_connection/g;
$new_repository =~ s/\{\{DEFAULT_ENTRIES}}/$default_entries/g;
$new_repository =~ s/\{\{COLUMNS_LIST_QUOTED}}/$column_names_quoted/g;
$new_repository =~ s/\{\{TABLE_STRUCT_COLUMNS}}/$table_struct_columns/g;
$new_repository =~ s/\{\{FIND_ONE_ENTRIES}}/$find_one_entries/g;
$new_repository =~ s/\{\{UPDATE_ONE_ENTRIES}}/$update_one_entries/g;
$new_repository =~ s/\{\{INSERT_ONE_ENTRIES}}/$insert_one_entries/g;
$new_repository =~ s/\{\{INSERT_MANY_ENTRIES}}/$insert_many_entries/g;
$new_repository =~ s/\{\{ALL_ENTRIES}}/$all_entries/g;
print $new_repository;
my $generated_repository = './common/repositories/' . $table_to_generate . '_repository.h';
my $cmake_generated_reference = $generated_repository;
$cmake_generated_reference =~ s/.\/common\///g;
$generated_repository_files .= $cmake_generated_reference . "\n";
open(FH, '>', $generated_repository) or die $!;
print FH $new_repository;
close(FH);
}
print "\n# Make sure to add generated repositories to common/CMakeLists.txt under the repositories section\n\n";
print $generated_repository_files . "\n";
sub trim {
my $string = $_[0];
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}
sub translate_mysql_data_type_to_c {
my $mysql_data_type = $_[0];
my $struct_data_type = "std::string";
if ($mysql_data_type =~ /tinyint/) {
$struct_data_type = 'int8';
}
elsif ($mysql_data_type =~ /smallint/) {
$struct_data_type = 'int16';
}
elsif ($mysql_data_type =~ /bigint/) {
$struct_data_type = 'int';
# Use regular int for now until we have 64 support
}
elsif ($mysql_data_type =~ /int/) {
$struct_data_type = 'int';
}
elsif ($mysql_data_type =~ /float|double|decimal/) {
$struct_data_type = 'float';
}
return $struct_data_type;
}