#!/usr/bin/perl

# Copyright (c) 2021-2025, PostgreSQL Global Development Group

use strict;
use warnings FATAL => 'all';

# make sure we are in a sane environment.
use DBI();
use DBD::Pg();
use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
use Getopt::Std;

my %opt;
getopts('d:b:s:veorauc', \%opt);

if (!(scalar %opt && defined $opt{s}))
{
	print <<EOT;
Usage:
$0 -d DATABASE -s SECTIONS [-b NUMBER] [-v] [-e] [-o] [-r] [-a] [-u]
-d DATABASE	-DATABASE
-b NUMBER	-number of repeats
-s SECTIONS	-sections, format	sid1[,sid2[,sid3[...]]]]
-v		-verbose (show SQL)
-e		-show explain
-r		-use RD-tree index
-a		-AND section
-o		-show output
-u		-unique
-c		-count

EOT
	exit;
}

$opt{d} ||= '_int4';
my $dbi = DBI->connect('DBI:Pg:dbname=' . $opt{d});

my %table;
my @where;

$table{message} = 1;

if ($opt{a})
{
	if ($opt{r})
	{
		push @where, "message.sections @ '{$opt{s}}'";
	}
	else
	{
		foreach my $sid (split(/[,\s]+/, $opt{s}))
		{
			push @where, "message.mid = msp$sid.mid";
			push @where, "msp$sid.sid = $sid";
			$table{"message_section_map msp$sid"} = 1;
		}
	}
}
else
{
	if ($opt{r})
	{
		push @where, "message.sections && '{$opt{s}}'";
	}
	else
	{
		$table{message_section_map} = 1;
		push @where, "message.mid = message_section_map.mid";
		push @where, "message_section_map.sid in ($opt{s})";
	}
}

my $outf;
if ($opt{c})
{
	$outf =
	  ($opt{u}) ? 'count( distinct message.mid )' : 'count( message.mid )';
}
else
{
	$outf = ($opt{u}) ? 'distinct( message.mid )' : 'message.mid';
}
my $sql =
	"select $outf from "
  . join(', ', keys %table)
  . " where "
  . join(' AND ', @where) . ';';

if ($opt{v})
{
	print "$sql\n";
}

if ($opt{e})
{
	my @plan =
	  map { "$_->[0]\n" } @{ $dbi->selectall_arrayref("explain $sql") };
	print @plan;
}

my $t0 = [gettimeofday];
my $count = 0;
my $b = $opt{b};
$b ||= 1;
my @a;
foreach (1 .. $b)
{
	@a = exec_sql($dbi, $sql);
	$count = $#a;
}
my $elapsed = tv_interval($t0, [gettimeofday]);
if ($opt{o})
{
	foreach (@a)
	{
		print "$_->{mid}\t$_->{sections}\n";
	}
}
print sprintf(
	"total: %.02f sec; number: %d; for one: %.03f sec; found %d docs\n",
	$elapsed, $b, $elapsed / $b,
	$count + 1);
$dbi->disconnect;

sub exec_sql
{
	my ($dbi, $sql, @keys) = @_;
	my $sth = $dbi->prepare($sql) || die;
	$sth->execute(@keys) || die;
	my $r;
	my @row;
	while (defined($r = $sth->fetchrow_hashref))
	{
		push @row, $r;
	}
	$sth->finish;
	return @row;
}
