#!/usr/bin/env perl
# Benchmark the prepare_cached migration in DBIx::Fast's read methods.
#
# Compares four strategies for the same workload, against SQLite and
# (optionally) MariaDB:
#
#   raw_select   selectall_arrayref / selectrow_array     (re-parses every call)
#   raw_prepare  prepare + execute + fetch + finish        (manual, no cache)
#   raw_cached   prepare_cached + execute + fetch + finish (current impl)
#   dbix_fast    $db->all / $db->hash / $db->val           (public API)
#
# Run:
#   perl -Ilib examples/benchmark-statement-cache.pl                  # SQLite :memory:
#   perl -Ilib examples/benchmark-statement-cache.pl -i 20000 -r 500
#   DBIX_FAST_BENCH_MARIADB=mariadb://root:test@127.0.0.1:3307/bench \
#       perl -Ilib examples/benchmark-statement-cache.pl
use v5.38;
use Benchmark qw(cmpthese);
use DBI;
use DBIx::Fast;
use Getopt::Long;

my $seconds = 2;     # wall time per case (Benchmark negative-count mode)
my $rows    = 200;
my $mariadb = $ENV{DBIX_FAST_BENCH_MARIADB};

GetOptions(
    'time|t=i'  => \$seconds,
    'rows|r=i'  => \$rows,
    'mariadb=s' => \$mariadb,
) or die "usage: $0 [-t SECONDS] [-r ROWS] [--mariadb DSN]\n";

# Benchmark.pm convention: negative count = run for N CPU seconds per case.
my $duration = -$seconds;

run_bench(
    name => 'SQLite (:memory:)',
    dsn  => 'dbi:SQLite:dbname=:memory:',
    user => '',
    pass => '',
    quote_id_open  => '"',
    quote_id_close => '"',
    autoinc        => 'INTEGER PRIMARY KEY AUTOINCREMENT',
);

if ($mariadb) {
    run_bench(
        name => 'MariaDB',
        dsn_uri => $mariadb,
        quote_id_open  => '`',
        quote_id_close => '`',
        autoinc        => 'INT PRIMARY KEY AUTO_INCREMENT',
    );
}
else {
    print "\n[skipping MariaDB benchmark — set --mariadb DSN or "
        . "DBIX_FAST_BENCH_MARIADB env var to include it]\n";
}

sub run_bench (%opt) {
    print "\n", "=" x 72, "\n";
    print "Backend: $opt{name}\n";
    print "duration=${seconds}s per case  rows=$rows\n";
    print "=" x 72, "\n";

    # Build the DBIx::Fast handle (handles URI form too)
    my $db = $opt{dsn_uri}
        ? DBIx::Fast->new(dsn => $opt{dsn_uri}, RaiseError => 1)
        : DBIx::Fast->new(
            dsn        => $opt{dsn},
            driver     => 'SQLite',
            RaiseError => 1,
        );

    my $dbh = $db->db->dbh;

    # Schema + data
    $dbh->do('DROP TABLE IF EXISTS bench');
    $dbh->do(qq{
        CREATE TABLE bench (
            id    $opt{autoinc},
            name  VARCHAR(64),
            value INT
        )
    });
    my $ins = $dbh->prepare('INSERT INTO bench (name, value) VALUES (?, ?)');
    $dbh->begin_work;
    $ins->execute("row_$_", $_) for 1..$rows;
    $dbh->commit;

    # SQL templates use the driver's quoting style; matches what
    # DBIx::Fast will produce internally so the cache key aligns.
    my $qo = $opt{quote_id_open};
    my $qc = $opt{quote_id_close};
    my $sql_all  = qq{SELECT id, name, value FROM ${qo}bench${qc} WHERE id < ?};
    my $sql_one  = qq{SELECT id, name, value FROM ${qo}bench${qc} WHERE id = ?};
    my $sql_val  = qq{SELECT name FROM ${qo}bench${qc} WHERE id = ?};

    my $limit_id = int($rows / 2);

    # ---- all() / multi-row fetch ----
    print "\n--- all() multi-row (WHERE id < $limit_id) ---\n";
    cmpthese($duration, {
        raw_select  => sub {
            my $r = $dbh->selectall_arrayref($sql_all, {Slice => {}}, $limit_id);
        },
        raw_prepare => sub {
            my $sth = $dbh->prepare($sql_all);
            $sth->execute($limit_id);
            my $r = $sth->fetchall_arrayref({});
            $sth->finish;
        },
        raw_cached  => sub {
            my $sth = $dbh->prepare_cached($sql_all);
            $sth->execute($limit_id);
            my $r = $sth->fetchall_arrayref({});
            $sth->finish;
        },
        dbix_fast   => sub {
            $db->all($sql_all, $limit_id);
        },
    });

    # ---- hash() / single-row fetch ----
    print "\n--- hash() single-row (WHERE id = 1) ---\n";
    cmpthese($duration, {
        raw_select  => sub {
            my @r = $dbh->selectrow_array($sql_one, undef, 1);
        },
        raw_prepare => sub {
            my $sth = $dbh->prepare($sql_one);
            $sth->execute(1);
            my $r = $sth->fetchrow_hashref;
            $sth->finish;
        },
        raw_cached  => sub {
            my $sth = $dbh->prepare_cached($sql_one);
            $sth->execute(1);
            my $r = $sth->fetchrow_hashref;
            $sth->finish;
        },
        dbix_fast   => sub {
            $db->hash($sql_one, 1);
        },
    });

    # ---- val() / single-scalar fetch ----
    print "\n--- val() single-scalar (WHERE id = 1) ---\n";
    cmpthese($duration, {
        raw_select  => sub {
            my ($v) = $dbh->selectrow_array($sql_val, undef, 1);
        },
        raw_prepare => sub {
            my $sth = $dbh->prepare($sql_val);
            $sth->execute(1);
            my @r = $sth->fetchrow_array;
            $sth->finish;
        },
        raw_cached  => sub {
            my $sth = $dbh->prepare_cached($sql_val);
            $sth->execute(1);
            my @r = $sth->fetchrow_array;
            $sth->finish;
        },
        dbix_fast   => sub {
            $db->val($sql_val, 1);
        },
    });

    # ---- Cache footprint ----
    my $cache = $dbh->{CachedKids} // {};
    print "\nCachedKids entries after benchmark: ", scalar(keys %$cache), "\n";

    $dbh->do('DROP TABLE IF EXISTS bench');
}

__END__

=head1 NAME

bench-statement-cache.pl - benchmark DBIx::Fast's prepare_cached migration

=head1 SYNOPSIS

    perl -Ilib examples/benchmark-statement-cache.pl [options]

    Options:
      -t, --time N         CPU seconds per case (default 2; Benchmark negative-count mode)
      -r, --rows N         how many rows to seed the table with (default 200)
      --mariadb DSN        also benchmark against this MariaDB DSN

    Environment:
      DBIX_FAST_BENCH_MARIADB  same as --mariadb

=head1 DESCRIPTION

For each backend, runs three workloads (multi-row, single-row hash, single
scalar) across four strategies and prints a comparative table:

=over 4

=item raw_select  - C<selectall_arrayref> / C<selectrow_array> (re-parses every call)

=item raw_prepare - C<prepare> + execute + fetch + finish (no cache)

=item raw_cached  - C<prepare_cached> + execute + fetch + finish (current impl)

=item dbix_fast   - C<< $db->all / $db->hash / $db->val >> (public API)

=back

The gap between C<raw_prepare> and C<raw_cached> isolates the gain from
caching; the gap between C<raw_cached> and C<dbix_fast> measures DBIx::Fast's
wrapper overhead.

=cut
