#!d:/emx/bin/perl.exe
#
# Test of creating a simple table and inserting $record_count records in it,
# $opt_loop_count rows in order, $opt_loop_count rows in reverse order and
# $opt_loop_count rows in random order
#
# changes made for Oracle compatibility
# - $limits{'func_odbc_mod'} is OK from crash-me, but it fails here so set we
#   set it to 0 in server-cfg
# - the default server config runs out of rollback segments, so I added a couple
#   of disconnect/connects to reset
##################### Standard benchmark inits ##############################

use DBI;
use Benchmark;

$opt_loop_count=10000;		# number of rows/3
$small_loop_count=10;		# Loop for full table retrieval

chomp($pwd = `pwd`); $pwd = "." if ($pwd eq '');
require "$pwd/bench-init.pl" || die "Can't read Configuration file: $!\n";

if ($opt_loop_count < 100)
{
  $opt_loop_count=100;		# Some tests must have some data to work!
}

print "Testing the speed of inserting data into 1 table and do some selects on it.\n";
print "The tests are done with a table that has $opt_loop_count rows.\n\n";


####
#### Generating random keys
####

print "Generating random keys\n";
$random[$opt_loop_count]=0;
for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $random[$i]=$i+$opt_loop_count;
}

$tmpvar=1;
for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $tmpvar^= ((($tmpvar + 63) + $i)*3 % $opt_loop_count);
  $swap=$tmpvar % $opt_loop_count;
  $tmp=$random[$i]; $random[$i]=$random[$swap]; $random[$swap]=$tmp;
}

$total_rows=$opt_loop_count*3;

####
####  Connect and start timeing
####
$start_time=new Benchmark;
$dbh = DBI->connect($server->{'data_source'}, $opt_user, $opt_password,
		    { PrintError => 0}) || die $DBI::errstr;

####
#### Create needed tables
####

goto select_test if ($opt_skip_create);

print "Creating tables\n";
$sth = $dbh->do("drop table bench1");
do_many($dbh,$server->create("bench1",
			     ["id int NOT NULL"],
			     ["primary key (id)"]));

if ($opt_lock_tables)
{
  $sth = $dbh->do("LOCK TABLES bench1 WRITE") || die $DBI::errstr;
}

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

####
#### Insert $total_rows records in order, in reverse order and random.
####

$loop_time=new Benchmark;

if ($opt_fast_insert)
{
  $query="insert into bench1 values (";
}
else
{
  $query="insert into bench1 (id) values (";
}

print "Inserting $opt_loop_count rows in order\n";
for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $sth = $dbh->do($query . $i . ")") or die $DBI::errstr;
}

print "Inserting $opt_loop_count rows in reverse order\n";
for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $sth = $dbh->do($query . ($total_rows-1-$i) . ")")
    or die $DBI::errstr;
}

print "Inserting $opt_loop_count rows in random order\n";

for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $sth = $dbh->do($query . $random[$i] . ")") or die $DBI::errstr;
}

$end_time=new Benchmark;
print "Time for insert (" . ($total_rows) . "): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

####
#### insert $opt_loop_count records with duplicate id
####

print "Testing insert of duplicates\n";
$loop_time=new Benchmark;
for ($i=0 ; $i < $opt_loop_count ; $i++)
{
  $tmpvar^= ((($tmpvar + 63) + $i)*3 % $opt_loop_count);
  $tmp=$tmpvar % ($total_rows);
  $tmpquery = "$query" . "$tmp" . ")";
  if ($dbh->do($tmpquery))
  {
    die "Didn't get an error when inserting duplicate record $tmp\n";
  }
}

$end_time=new Benchmark;
print "Time for insert_duplicates (" . ($total_rows) . "): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

####
#### Do some selects on the table
####

select_test:

print "Retrieving data from the table\n";
$loop_time=new Benchmark;
$error=0;

# It's really a small table, so we can try a select on everything

for ($i=1 ; $i < $small_loop_count ; $i++)
{
  if (($found_rows=fetch_all_rows($dbh,"select * from bench1")) !=
      $total_rows)
  {
    if (!$error++)
    {
      print "Warning: Got $found_rows rows when selecting a hole table of " . ($total_rows) . " rows\nContact the database or DBD author!\n";
    }
  }
}

$end_time=new Benchmark;
print "Time for select_big ($small_loop_count): " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n";

$loop_time=new Benchmark;
$estimated=0;
for ($i=1 ; $i < $small_loop_count/2 ; $i++)
{
  fetch_all_rows($dbh,"select * from bench1 order by id") or die $DBI::errstr;
  fetch_all_rows($dbh,"select * from bench1 order by id desc") or die $DBI::errstr;
  $end_time=new Benchmark;
  last if ($estimated=predict_query_time($loop_time,$end_time,\$count,$count,
					 $small_loop_count));
}
if ($estimated)
{ print "Estimated time"; }
else
{ print "Time"; }
print " for order_by ($small_loop_count): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n";

# Test select that is very popular when using ODBC

$columns=min($limits->{'max_columns'},50,($limits->{'query_size'}-50)/13);
$columns=$columns- ($columns % 4);	# Make Divisible by 4

$estimated=0;
$loop_time=new Benchmark;
for ($count=0 ; $count < $small_loop_count ; )
{
  for ($rowcnt=0; $rowcnt <= $columns; $rowcnt+= $columns/4)
  {
    $query="select * from bench1 where ";
    $or_part= "id = 1";
    $count+=2;

    for ($i=1 ; $i < $rowcnt ; $i++)
    {
      $tmpvar^= ((($tmpvar + 63) + $i)*3 % $opt_loop_count);
      $tmp=$tmpvar % ($opt_loop_count*4);
      $or_part.=" or id=$tmp";
    }
    print $query . $or_part . "\n" if ($opt_debug);
    fetch_all_rows($dbh,$query . $or_part) or die $DBI::errstr;

    if ($limits->{'func_extra_in_num'})
    {
      $in_part=$or_part;	# Same query, but use 'func_extra_in_num' instead.
      $in_part=~ s/ = / IN \(/;
      $in_part=~ s/ or id=/,/g;
      $in_part.= ")";
      fetch_all_rows($dbh,$query . $in_part) or die $DBI::errstr;
      $count++;
    }
# Do it a little harder by setting a extra range
    defined(fetch_all_rows($dbh,"$query($or_part) and id < 10")) or die $DBI::errstr;
  }
  $end_time=new Benchmark;
  last if ($estimated=predict_query_time($loop_time,$end_time,\$count,$count,
					 $small_loop_count));
}

if ($estimated)
{ print "Estimated time"; }
else
{ print "Time"; }
print " for select_range ($count): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n";

# Do some sample selects on direct key

$loop_time=new Benchmark;
for ($i=0 ; $i < $opt_loop_count; $i++)
{
  $tmpvar^= ((($tmpvar + 63) + $i)*3 % $opt_loop_count);
  $tmp=$tmpvar % ($total_rows);
  fetch_all_rows($dbh,"select * from bench1 where id=$tmp")
    or die $DBI::errstr;
}

# Do some sample selects on keys that isn't found
for ($i=0 ; $i < $opt_loop_count; $i++)
{
  $tmpvar^= ((($tmpvar + 63) + $i)*3 % $opt_loop_count);
  $tmp=$tmpvar % ($total_rows)+$total_rows;
  defined(fetch_all_rows($dbh,"select * from bench1 where id=$tmp")) or die $DBI::errstr;
  die "Found rows on impossible id: $tmp - $sth\n" if ($sth eq "OEO");
}

$end_time=new Benchmark;
print "Time for select_key (" . ($opt_loop_count*2) . "): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

####
#### A lot of simple selects on ranges
####

@Q=("select * from bench1 where id=3 or id=2 or id=1 or id=4 or id=16 or id=10",
    6,
    "select * from bench1 where id>=" . ($total_rows-1) ." or id<1",
    2,
    "select * from bench1 where id>=1 and id<=2",
    2,
    "select * from bench1 where (id>=1 and id<=2) or (id>=1 and id<=2)",
    2,
    "select * from bench1 where id>=1 and id<=10 and id<=5",
    5,
    "select * from bench1 where (id>0 and id<2) or id>=" . ($total_rows-1),
    2,
    "select * from bench1 where (id>0 and id<2) or (id>= " . ($opt_loop_count/2) . " and id <= " . ($opt_loop_count/2+2) . ") or id = " . ($opt_loop_count/2-1),
    5,
    "select * from bench1 where (id>=5 and id<=10) or (id>=1 and id<=4)",
    10,
    "select * from bench1 where (id=1 or id=2) and (id=3 or id=4)",
    0,
    "select * from bench1 where (id=1 or id=2) and (id=2 or id=3)",
    1,
    "select * from bench1 where (id=1 or id=5 or id=20 or id=40) and (id=1 or id>=20 or id=4)",
    3,
    "select * from bench1 where ((id=1 or id=3) or (id>1 and id<3)) and id<=2",
    2,
    "select * from bench1 where (id >= 0 and id < 4) or (id >=4 and id < 6)",
    6,
    "select * from bench1 where id <= -1 or (id >= 0 and id <= 5) or (id >=4 and id < 6) or (id >=6 and id <=7) or (id>7 and id <= 8)",
    9,
    "select * from bench1 where (id>=1 and id<=2 or id>=4 and id<=5) or (id>=0 and id <=10)",
    11,
    "select * from bench1 where (id>=1 and id<=2 or id>=4 and id<=5) or (id>2 and id <=10)",
    10,
    "select * from bench1 where (id>1 or id <1) and id<=2",
    2,
    "select * from bench1 where id <= 2 and (id>1 or id <=1)",
    3,
    "select * from bench1 where (id>=1 or id <1) and id<=2",
    3,
    "select * from bench1 where (id>=1 or id <=2) and id<=2",
    3
    );

print "Test of compares with simple ranges\n";

$loop_time=new Benchmark;
$count=0;
for ($test=0 ; $test < $small_loop_count; $test++)
{
  $count+=$#Q+1;
  for ($i=0 ; $i < $#Q ; $i+=2)
  {
    my $query=$Q[$i];
    my $rows=$Q[$i+1];
    if (($row_count=fetch_all_rows($dbh,$query)) != $rows)
    {
      if ($row_count == undef())
      {
	die "Got error: $DBI::errstr when executing $query\n";
      }
      die "'$query' returned wrong number of rows: $row_count instead of $rows\n";
    }
  }
}

$end_time=new Benchmark;
print "Time for select_range ($count): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

####
#### Some group queries
####

if ($limits->{'group_functions'})
{
  $loop_time=new Benchmark;
  $count=1;

  $sth=$dbh->prepare($query="select count(*) from bench1") or die $DBI::errstr;
  $sth->execute or die $sth->errstr;
  if (($sth->fetchrow_array)[0] != $total_rows)
  {
    die "'$query' returned wrong result\n";
  }
  $sth->finish;

  $count++;
  $sth=$dbh->prepare($query="select count(*) from bench1 where id >= " .
		 ($opt_loop_count*2)) or die $DBI::errstr;
  $sth->execute or die $DBI::errstr;
  if (($sth->fetchrow_array)[0] != $opt_loop_count)
  {
    die "'$query' returned wrong result\n";
  }
  $sth->finish;

  $count++;
  $sth=$dbh->prepare($query="select count(*),sum(id),min(id),max(id),avg(id) from bench1") or die $DBI::errstr;
  $sth->execute or die $DBI::errstr;
  @row=$sth->fetchrow_array;
  if ($row[0] != $total_rows ||
      $row[1] != (($total_rows-1)/2*$total_rows) ||
      $row[2] != 0 ||
      $row[3] != $total_rows-1 ||
      int($row[4]) != int(($total_rows-1)/2))
  {
    die "'$query' returned wrong result: @row\n";
  }
  $sth->finish;

  if ($limits->{'func_odbc_mod'})
  {
    $tmp="mod(id,10)";
    if ($limits->{'func_extra_%'})
    {
      $tmp="id % 10";				# For postgreSQL
    }
    $count++;
    if (fetch_all_rows($dbh,$query=$server->query("select $tmp as last_digit,count(*) from bench1 group by last_digit")) != 10)
    {
      die "'$query' returned wrong result\n";
    }
  }
  if ($limits->{'order_by_position'} && $limits->{'group_by_position'})
  {
    $count++;
    if (fetch_all_rows($dbh, $query="select id,id+1,id+2 from bench1 where id < 100 group by 1,2,3 order by 1 desc,2,3") != 100)
    {
      die "'$query' returned wrong result\n";
    }
  }
  $end_time=new Benchmark;
  print "Time for select_group ($count): " .
      timestr(timediff($end_time, $loop_time),"noc") . "\n\n";
}


####
#### Some updates on the table
####

$loop_time=new Benchmark;

$count=0;
if ($limits->{'functions'})
{
  print "Testing update with functions\n";
  for ($i=0 ; $i < $opt_loop_count ; $i++)
  {
    $tmp=$opt_loop_count+$random[$i]; # $opt_loop_count*2 <= $tmp < $total_rows
    $sth = $dbh->do("update bench1 set id=-$tmp where id=$tmp") or die $DBI::errstr;
  }
  $count+=$opt_loop_count;

  $end_time=new Benchmark;
  print "Time for update_key ($opt_loop_count):  " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

  if ($opt_fast && $opt_server eq "pg")
  {
    $server->vacuum($dbh);
  }

  ## Oracle runs out of rollback segments here if using the default "small" configuration
  ## so disconnect and reconnect to use a new segment
  if ( $opt_server eq "Oracle" ){
    $dbh->disconnect;				# close connection
    $dbh = DBI->connect($server->{'data_source'}, $opt_user, $opt_password) || die $DBI::errstr;
  }

  $loop_time=new Benchmark;
  for ($i= 0 ; $i <= $opt_loop_count/2 ; $i+=10)
  {
    $count++;
    $sth=$dbh->do("update bench1 set id= 0-id where id >= 0 and id <= $i") or die $DBI::errstr;
  }

  if ($opt_fast && $opt_server eq "pg")
  {
    $server->vacuum($dbh);
  }

  $sth=$dbh->do("update bench1 set id= 0-id where id >= 0 and id <= $opt_loop_count") or die $DBI::errstr;

  if ($opt_fast && $opt_server eq "pg")
  {
    $server->vacuum($dbh);
  }

  $count++;
  $sth=$dbh->do("update bench1 set id= 0-id where id >= $opt_loop_count and id <= ". ($opt_loop_count*2)) or die $DBI::errstr;

  if ($opt_fast && $opt_server eq "pg")
  {
    $server->vacuum($dbh);
  }

  #
  # Check that everything except id=0 was updated
  # In principle we shouldn't time this in the update loop..
  #
  if (($row_count=fetch_all_rows($dbh,$query="select * from bench1 where id>=0")) != 1)
  {
    print "Warning: Wrong information after update: Found '$row_count' rows, but should have been: 1\n";
  }

  #restore id to 0 <= id < $total_rows
  $count++;
  $sth=$dbh->do($query="update bench1 set id= 0-id where id<0") or die $DBI::errstr;

  $end_time=new Benchmark;
  print "Time for update_key_big ($count): " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n\n";
}
else
{
  print "Testing update loops\n";
  #
  # This is for mSQL that doesn't have functions. Do we really need this ????
  #

  $sth=$dbh->prepare("select id from bench1 where id >= 0") or die $DBI::errstr;
  $sth->execute or die $DBI::errstr;
  while (@tmp = $sth->fetchrow_array)
  {
    $tmp1 = "-$tmp[0]";
    $count++;
    $sth1 = $dbh->do("update bench1 set id = $tmp1 where id = $tmp[0]");
    $end_time=new Benchmark;
    if (($end_time->[0] - $loop_time->[0]) > $opt_time_limit)
    {
      print "note: Aborting update loop because of timeout\n";
      last;
    }
  }
  $sth->finish;
  # Check that everything except id=0 was updated
  # In principle we shouldn't time this in the update loop..
  #
  if (fetch_all_rows($dbh,$query="select * from bench1 where id>=0") != 1)
  {
    if ($count == $total_rows)
    {
      print "Warning: Wrong information after update: Found '$row_count' rows, but should have been: 1\n";
    }
  }
  #restore id to 0 <= id < $total_rows
  $sth=$dbh->prepare("select id from bench1 where id < 0") or die $DBI::errstr;
  $sth->execute or die $DBI::errstr;
  while (@tmp = $sth->fetchrow_array)
  {
    $count++;
    $tmp1 = 0-$tmp[0];
    $sth1 = $dbh->do("update bench1 set id = $tmp1 where id = $tmp[0]");
  }
  $sth->finish;
  $end_time=new Benchmark;
  $estimated=predict_query_time($loop_time,$end_time,\$count,$count-1,
				$opt_loop_count*6-1);
  if ($estimated)
  { print "Estimated time"; }
  else
  { print "Time"; }
  print " for update_key ($count): " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n\n";
}

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

## Oracle runs out of rollback segments here if using the default "small" configuration
## so disconnect and reconnect to use a new segment
if ( $opt_server eq "Oracle" ){
  $dbh->disconnect;				# close connection
  $dbh = DBI->connect($server->{'data_source'}, $opt_user, $opt_password) || die $DBI::errstr;
}

####
#### Do some deletes on the table
####

if (!$opt_skip_delete)
{
  print "Testing delete\n";
  $loop_time=new Benchmark;
  for ($i=0 ; $i < $opt_loop_count ; $i++)
  {
    $tmp=$opt_loop_count+$random[$i]; # $opt_loop_count*2 <= $tmp < $total_rows
    $dbh->do("delete from bench1 where id=$tmp") or die $DBI::errstr;
  }

  $end_time=new Benchmark;
  print "Time for delete ($opt_loop_count): " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n";

  if ($opt_fast && $opt_server eq "pg")
  {
    $server->vacuum($dbh);
  }

  $count=0;
  $loop_time=new Benchmark;
  for ($i= 0 ; $i < $opt_loop_count/2 ; $i+=10)
  {
    $sth=$dbh->do("delete from bench1 where id >= 0 and id <= $i") or die $DBI::errstr;
    $count++;
  }
  $count+=2;
  $sth=$dbh->do("delete from bench1 where id >= 0 and id <= $opt_loop_count") or die $DBI::errstr;

  if ($opt_fast)
  {
    $sth=$dbh->do("delete from bench1") or die $DBI::errstr;
  }
  else
  {
    $sth = $dbh->do("delete from bench1 where id < " . ($total_rows)) or die $DBI::errstr;
  }

  $end_time=new Benchmark;
  print "Time for delete_big ($count): " .
    timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

  if ($opt_lock_tables)
  {
    $sth = $dbh->do("UNLOCK TABLES ") || die $DBI::errstr;
  }
  $sth = $dbh->do("drop table bench1") or die $DBI::errstr;
}

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

#
# Test of insert in table with many keys
# This test assumes that the server really create the keys!
#

@fields=(); @keys=();
$keys=min($limits->{'max_index'},16);		# 16 is more than enough
$seg= min($limits->{'max_index_parts'},$keys,16);	# 16 is more than enough

print "Insert into table with $keys keys and with a primary key with $seg parts\n";

# Make keys on the most important types
@types=(0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1);	# A 1 for each char field
push(@fields,"field1 tinyint not null");
push(@fields,"field2 mediumint not null");
push(@fields,"field3 smallint not null");
push(@fields,"field4 char(16) not null");
push(@fields,"field5 integer not null");
push(@fields,"field6 float not null");
push(@fields,"field7 double not null");
for ($i=8 ; $i <= $keys ; $i++)
{
  push(@fields,"field$i char(5) not null");	# Should be relatively fair
}

# First key contains many segments
$query="primary key (";
for ($i= 1 ; $i <= $seg ; $i++)
{
  $query.= "field$i,";
}
substr($query,-1)=")";
push (@keys,$query);

#Create other keys
for ($i=2 ; $i <= $keys ; $i++)
{
  push(@keys,"index index$i (field$i)");
}

do_many($dbh,$server->create("bench1",\@fields,\@keys));
if ($opt_lock_tables)
{
  $dbh->do("LOCK TABLES bench1 WRITE") || die $DBI::errstr;
}

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

$loop_time=new Benchmark;
$fields=$#fields;
for ($i=0; $i < $opt_loop_count; $i++)
{
  $rand=$random[$i];
  $query="insert into bench1 values (" . ($i % 256) . ",$rand," . ($i & 65535) .
    ",'ABCDEF$rand',0,";

  for ($j=5; $j <= $fields ; $j++)
  {
    $query.= ($types[$j] == 0) ? "$rand," : "'$rand',";
  }
  substr($query,-1)=")";
  $dbh->do($query) or die "Got error $DBI::errstr with query: $query\n";
}

$end_time=new Benchmark;
print "Time for insert_key ($opt_loop_count): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

#
# update one key of the above
#

print "Testing update of keys\n";
$loop_time=new Benchmark;
for ($i=0 ; $i<256 ; $i++)
{
  $dbh->do("update bench1 set field5=1 where field1=$i")
    or die "Got error $DBI::errstr with query: $query\n";
}
$end_time=new Benchmark;
print "Time for update_key (256): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

## Oracle runs out of rollback segments here if using the default "small" configuration
## so disconnect and reconnect to use a new segment
if ( $opt_server eq "Oracle" ){
  $dbh->disconnect;				# close connection
  $dbh = DBI->connect($server->{'data_source'}, $opt_user, $opt_password) || die $DBI::errstr;
}

#
# Delete everything from table
#

print "Deleting everything from table\n";
$loop_time=new Benchmark;
$count=0;
if ($opt_fast)
{
  $dbh->do("delete from bench1") or die $DBI::errstr;
  $count++;
}
else
{
  $dbh->do("delete from bench1 where field1 > 0") or die $DBI::errstr;
  $dbh->do("delete from bench1 where field1 = 0") or die $DBI::errstr;
  $count+=2;
}

if ($opt_lock_tables)
{
  $sth = $dbh->do("UNLOCK TABLES") || die $DBI::errstr;
}

$end_time=new Benchmark;
print "Time for delete_big ($count): " .
  timestr(timediff($end_time, $loop_time),"noc") . "\n\n";

$sth = $dbh->do("drop table bench1") or die $DBI::errstr;
if ($opt_fast && $opt_server eq "pg")
{
  $server->vacuum($dbh);
}

####
#### End of benchmark
####

$dbh->disconnect;				# close connection

end_benchmark($start_time);
