#!/usr/bin/perl
use strict;

use Readonly;
use Data::Dumper;
use Data::Random qw( :all );
use Fatal qw(open close);

Readonly my $RECORDS => 1_000_000;

system("ps uw -p $$");
my $data = gen_data();
system("ps uw -p $$");

generate_copy_sql();

for my $i (1, map { $_ * 5 } (1..20)) {
    generate_inserts_sql($i, 1);
    generate_inserts_sql($i, 0);
    for my $j (map { $_ * 5 } (1..20)) {
        generate_multiinserts_sql($j, $i, 1);
        generate_multiinserts_sql($j, $i, 0);
    }
}

exit;

sub generate_multiinserts_sql {
    my ($values_per_statement, $statements_per_transaction, $prep) = @_;
    my $table = sprintf "test_multiinsert_%s%03u_%03u", $prep ? "p_" : "__", $values_per_statement, $statements_per_transaction;

    open my $fh, ">", "$table.sql";
    print $fh table("$table");


    my $sql = "INSERT INTO $table (id, some_text, bool_1, bool_2, some_date, some_timestamp) values ";
    my $sql_last = $sql;
    if ($prep) {
        print $fh "prepare ins as INSERT INTO $table (id, some_text, bool_1, bool_2, some_date, some_timestamp) values (\$1, \$2, \$3, \$4, \$5, \$6)";
        for my $i (1..$values_per_statement-1) {
            print $fh "\n    , (" . join(", ", map { '$' . ($i*6 + $_) } (1..6) ) . ")"
        }
        print $fh ";\n";
        $sql = "EXECUTE ins ";

        if (0 != $RECORDS % $values_per_statement) {
            my $fin_count = $RECORDS % $values_per_statement;
            print $fh "prepare inslast as INSERT INTO $table (id, some_text, bool_1, bool_2, some_date, some_timestamp) values (\$1, \$2, \$3, \$4, \$5, \$6)";
            for my $i (1..$fin_count-1) {
                print $fh "\n    , (" . join(", ", map { '$' . ($i*6 + $_) } (1..6) ) . ")"
            }
            print $fh ";\n";
            $sql_last = "EXECUTE inslast ";
        }
    }
    my $add_commit = 0;
    my $statements_count = 0;
    for (my $i = 0; $i + $values_per_statement <= $RECORDS; $i += $values_per_statement) {
        my @records;
        for my $j (0 .. $values_per_statement - 1 ) {
            my $record = $data->[ $i + $j ];
            my $record_data = sprintf q{%u, '%s', '%s', '%s', '%s', '%s'}, @{ $record };
            $record_data = "($record_data)" unless $prep;
            push @records, $record_data;
        }
        $statements_count++;
        if ($statements_per_transaction > 1 && $statements_count == 1) {
            print $fh "BEGIN;\n";
        }
        if ($prep) {
            print $fh "$sql (" . join(",\n    ", @records) . ");\n";
        } else {
            print $fh "$sql " . join(",\n    ", @records) . ";\n";
        }
        $add_commit = 1;
        if ($statements_per_transaction > 1 && $statements_count >= $statements_per_transaction) {
            print $fh "COMMIT;\n";
            $statements_count = 0;
            $add_commit = 0;
        }
    }
    my $fin_count = $RECORDS % $values_per_statement;
    if ($fin_count > 0) {
        my @records;
        for my $j ($RECORDS - $fin_count .. $RECORDS - 1) {
            my $record = $data->[ $j ];
            my $record_data = sprintf q{%u, '%s', '%s', '%s', '%s', '%s'}, @{ $record };
            $record_data = "($record_data)" unless $prep;
            push @records, $record_data;
        }
        if ($prep) {
            print $fh "$sql_last (" . join(",\n    ", @records) . ");\n";
        } else {
            print $fh "$sql_last " . join(",\n    ", @records) . ";\n";
        }
    }
    print $fh "COMMIT;\n" if $add_commit && $statements_per_transaction > 1;

    close $fh;
}

sub generate_inserts_sql {
    my ($i, $prep) = @_;
    my $table = sprintf "test_insert_%s%03u", $prep ? "p_" : "__", $i;
    open my $fh, ">", "$table.sql";
    print $fh table("$table");
    my $sql = "INSERT INTO $table (id, some_text, bool_1, bool_2, some_date, some_timestamp) values ";
    if ($prep) {
        printf $fh "prepare ins as INSERT INTO $table (id, some_text, bool_1, bool_2, some_date, some_timestamp) values (\$1, \$2, \$3, \$4, \$5, \$6);\n";
        $sql = "EXECUTE ins ";
    }

    my $j = 0;
    my $add_commit = 0;
    for my $rec ( @{ $data } ) {
        $j++;
        if ($j == 1 && $i > 1) {
            print $fh "BEGIN;\n";
        }
        printf $fh q{%s(%u, '%s', '%s', '%s', '%s', '%s')%s}, $sql, @{ $rec }, ";\n";
        $add_commit = 1 if $i > 1;
        if ($j >= $i && $i > 1) {
            print $fh "COMMIT;\n";
            $j = 0;
            $add_commit = 0;
        }
    }
    print $fh "COMMIT;\n" if $add_commit;

    close $fh;
}

sub generate_copy_sql {
    my $table = "test_copy";
    open my $fh, ">", "$table.sql";
    print $fh table("$table");
    print $fh "copy $table from stdin;\n";
    for my $rec ( @{ $data } ) {
        print $fh join("\t", @{ $rec } ) . "\n";
    }
    print $fh "\\.\n";
    close $fh;
}

sub table {
    my $name_part = shift;
    return sprintf("CREATE TABLE %s (id int4 primary key, some_text text, bool_1 bool, bool_2 bool, some_date date, some_timestamp timestamp);\n", $name_part);
}

sub gen_data {
    my $data = [];

    for my $i (1..$RECORDS) {

        my @record = ();
        push @record, $i;                                                                         # id
        push @record, join("", rand_chars( "set" => "alphanumeric", "min" => 5, "max" => 20 ));   # some_text
        push @record, rand() < 0.95 ? "t" : "f";                                                  # bool_1
        push @record, rand() < 0.25 ? "t" : "f";                                                  # bool_2
        push @record, rand_date();                                                                # some_date
        push @record, rand_datetime();                                                            # some_timestamp

        push @{$data}, \@record;
    }
    return $data;
}
