The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/my/gnu/bin/perl -w

# very slightly modified version of msql.t as in the MsqlPerl version
# 1.16

# Running the testscript with a hostname as $ARGV[0] runs the test via
# a TCP socket. Per default we connect to the unix socket to avoid
# problems you might have with resolving "localhost". Too many systems
# are configured wrong in this respect. But you're welcome to test it
# out.

my $host = shift @ARGV || $ENV{'DBI_HOST'} || "~~test_host~~";
my $user = shift @ARGV || $ENV{'DBI_USER'} || "~~test_user~~";
my $password = shift @ARGV || $ENV{'DBI_PASS'} || "~~test_pass~~";
my $dbname = shift @ARGV || $ENV{'DBI_DB'} || "~~test_db~~";

# That's the standard perl way tostart a testscript. It announces that
# that many tests are to follow. And it does so before anything can go
# wrong;

BEGIN {
    do ((-f "lib.pl") ? "lib.pl" : "t/lib.pl");
    if ($mdriver ne "mysql") { print "1..0\n"; exit 0; }
    print "1..68\n";
}

use Mysql;

# Force yourself to strict programming. See man strict for details.
# use strict;

# Variables we're going to use
my(
   $query,
   $firsttable,
   $secondtable,
   $dbh,
   $dbh2,
   $dbh3,
   $sth,
   $i,
   @row,
   %hash,
  );

# You may connect in two steps: (1) Connect and (2) SelectDB...

if ($dbh = Mysql->connect($host, $dbname, $user, $password)){
    print "ok 1\n";
} else {
    $Mysql::db_errstr ||= "";
    my $onhost = $host ? " (on $host)" : "";
    print STDERR qq{not ok 1: $Mysql::db_errstr
\tIt looks as if your server$onhost is not up and running.
\tThis test requires a running server.
\tPlease make sure your server is running and retry.
};
    exit;
}

if ($dbh->selectdb($dbname)){
    print("ok 2\n");
} else {
    die qq{not ok 2: $Mysql::db_errstr
    Please make sure that a database \"$dbname\" exists
    and that you have permission to read and write on it
};
}

# Or you may call connect with two arguments, the first being the
# host, and the second being the DB

if ($dbh = Mysql->connect($host,$dbname,$user,$password)){
    print("ok 3\n");
} else {
    die "not ok 3: $Mysql::db_errstr\n";
}

# For the error messages we're going to produce within this script we
# write a subroutine, so the typical error message will always look
# more or less similar:

sub test_error {
    my($id,$query,$error) = @_;
    $id    ||= "?";               # Newer Test::Harness will accept that
    $query ||= "";                # query is optional
    $query = "\n\tquery $query" if $query;
    $error ||= Mysql->errmsg;      # without error we ask Mysql
    print qq{Not ok $id:\n\terrmsg $error$query\n};
}


# Now we create two tables that are certainly not in the test database
# If you don't understand the trickery here, just skip this section, No big deal.
{
    my $goodtable = "TABLE00";
    my(%foundtable,@foundtable);
    @foundtable  =  $dbh->listtables;
    @foundtable{@foundtable} = (1) x @foundtable; # all existing tables are now keys in %foundtable
    my $limit = 0;

    for ($firsttable, $secondtable) {
	while () {
	    next if $foundtable{++$goodtable};
	    my $query = qq{
		create table $goodtable (
					 she char(32),
					 him char(32) not null,
					 who char (32)
					)
	    };
	    unless ($dbh->query($query)){
		die "Cannot create table: query [$query] message [$Mysql::db_errstr]\n" if $limit++ > 1000;
		next;
	    }
	    $_ = $goodtable;
	    last;
	}
    }
    # For the tests in this script we have two tablenames that we can
    # peruse: $firsttable and $secondtable
}

# Now we write some test records into the two tables. Note, we *know*,
# these tables are empty

print "Writing some test records.\n";
for $query (
	    "insert into $firsttable values ('Anna', 'Franz', 'Otto')"        ,
	    "insert into $firsttable values ('Sabine', 'Thomas', 'Pauline')"  ,
	    "insert into $firsttable values ('Jane', 'Paul', 'Jah')"	      ,
	    "insert into $secondtable values ('Henry', 'Francis', 'James')"   ,
	    "insert into $secondtable values ('Cashrel', 'Beco', 'Lotic')"
	   ) {
    $dbh->query($query) or test_error(0,$query);
}

$query = "select * from $firsttable";
$sth = $dbh->query($query) or test_error(0,$query);

($sth->numrows == 3)   and print("ok 4\n") or print("not ok 4\n"); # three rows
($sth->numfields == 3) and print("ok 5\n") or print("not ok 5\n"); # three columns

# There is the array reference $sth->name. It has to have as many
# fields as $sth->numfields tells us
print "Checking numfields.\n";
(@{$sth->name} == $sth->numfields)
    and print ("ok 6\n") or print("not ok 6\n");

# There is the array reference $sth->table. We expect, that all three
# fields in the array have the same value, as we only selected from
# $firsttable
print "Checking table.\n";
$sth->table->[0] eq $firsttable
    and print ("ok 7\n") or print("not ok 7\n");
$sth->table->[1] eq $sth->table->[2]
    and print ("ok 8\n") or print("not ok 8\n");

# CHAR_TYPE, NUM_TYPE and REAL_TYPE are exported functions from
# Mysql. That is why you have to say 'use Mysql'. The functions are
# really constants, but that's the way headerfile constants are
# handled in perl5 up to 5.001m (will probably change soon)
print "Checking type.\n";
CHAR_TYPE() == $sth->type->[0]
    and print ("ok 9\n") or print("not ok 9\n");

print "Checking number of rows.\n";
{
    # Now we count the rows ourselves, we don't trust anybody
    my $rowcnt=0;
    while (@row = $sth->fetchrow()){
	$rowcnt++;
    }

    # We haven't yet tested DataSeek, so lets count again
    $sth->dataseek(0);
    while (@row = $sth->fetchrow()){
	$rowcnt++;
    }

    # $rowcount now==6, twice the number of rows we've seen
    ($rowcnt/2 == $sth->numrows)
	and print ("ok 10\n") or print("not ok 10\n");
}


# let's see the second table
$sth = $dbh->query("select * from $secondtable") or test_error();

# We set the second field "not null". Does the API know that?
$sth->is_not_null->[1] > 0
    and print ("ok 11\n") or print("not ok 11\n");

# Are we able to just reconnect with the *same* scalar ($dbh) playing
# the role of the db-handle?
if ($dbh = Mysql->connect($host,$dbname,$user,$password)){
    print("ok 12\n");
} else {
    print "not ok 12: $Mysql::db_errstr\n";
}

# We may have an arbitrary number of statementhandles. Each
# statementhandle consumes memory, so in reality we try to scope them
# with my() within a block or we reuse them or we undef them.
{
    # Declare the statement handle as lexically scoped (see man
    # perlfunc and search for 'my EXPR') Don't forget to scope other
    # variables too, that you won't need outside the block
    my($sth1,$sth2,@row1,$count);

    $sth1 = $dbh->query("select * from $firsttable")
	or warn "Query had some problem: $Mysql::db_errstr\n";
    $sth2 = $dbh->query("select * from $secondtable")
	or warn "Query had some problem: $Mysql::db_errstr\n";

    # You have seen this above, so NO COMMENT :)
    $count=0;
    while ($sth2->fetchrow and @row1 = $sth1->fetchrow){
	$count++;
    }
    $count == 2  and print ("ok 13\n") or print("not ok 13\n");

    # When we undef this handle, the memory associated with it is
    # freed
    undef ($sth2);

    $count=0;
    while (@row1 = $sth1->fetchrow){
	$count++;
    }
    $count == 1 and print ("ok 14\n") or print("not ok 14\n");

    # When we leave this block, the memory associated with $sth1 is
    # freed
}

# What happens, when we have errors?
# Yes, there's a typo: we add a paren to the statement
{
    # The use of the -w switch is really a good idea in general, but
    # if you want the -w switch but do NOT want to see Mysql's error
    # messages, you can turn them off using $Mysql::QUIET

    local($Mysql::QUIET) = 1;
    # In reality we would say "or die ...", but in this case we forgot it:
    $sth = $dbh->query  ("select * from $firsttable
	     where him = 'Thomas')");

    # $Mysql::db_errstr should contain the word "error" now
    $dbh->errmsg =~ /error/
	and print("ok 15\n") or print("not ok 15\n");
}



# Now $sth should be undefined, because the query above failed. If we
# try to use this statementhandle, we should die. We don't want to
# die, because we are in atest script. So we check what happens with
# eval
eval "\@row = \$sth->fetchrow;";
if ($@){print "ok 16\n"} else {print "not ok 16\n"}


# Remember, we inserted a row into table $firsttable ('Sabine',
# 'Thomas', 'Pauline'). Let's see, if they are still there.
$sth = $dbh->query  ("select * from $firsttable
     where him = 'Thomas'")
     or warn "query had some problem: $Mysql::db_errstr\n";

@row = $sth->fetchrow or warn "$firsttable didn't find a matching row";
$row[2] eq "Pauline" and print ("ok 17\n") or print("not ok 17\n");

{
    # %fieldnum is a hash that associates the index number for each field
    # name:
    my %fieldnum;
    @fieldnum{@{$sth->name}} = 0..@{$sth->name}-1;
    
    # %fieldnum is now (she => 0, him => 1, who => 2)
    
    # So we do not have to hard-code the zero for "she" here
    $row[$fieldnum{"she"}] eq 'Sabine'
	and print ("ok 18\n") or print("not ok 18\n");
}

# After 18 tests, the database handle may feel the desire to rest. Or
# maybe the writer of this script has forgotten, that he is already
# connected

# While in reality you should use your database connections
# economically -- they cost you a slot in the server connection table,
# and you can easily run out of available slots -- we, in the test
# script want to know what happens with more than one handle
if ($dbh2 = Mysql->connect($host,$dbname,$user,$password)){
    print("ok 19\n");
} else {
    print "not ok 19\n";
}

# Some quick checks about the contents of the handle...
$dbh2->database eq $dbname and print("ok 20\n") or print("not ok 20\n");
$dbh2->sock =~ /^\d+$/ and print("ok 21\n") or print("not ok 21\n");

# Is $dbh2 able to drop a table, while we are connected with $dbh?
# Sure it can...
$dbh2->query("drop table $secondtable") and print("ok 22\n") or print("not ok 22\n");


{
    # Does ListDBs find the test database? Sure...
    my @array = $dbh2->listdbs;
    grep( /^$dbname$/, @array ) and print("ok 23\n") or print("not ok 23\n");

    # Does ListTables now find our $firsttable?
    @array = $dbh2->listtables;
    grep( /^$firsttable$/, @array )  and print("ok 24\n") or print("not ok 24\n");
}

# The third connection within a single script. I promise, this will do...
if ($dbh3 = Connect Mysql($host,$dbname,$user,$password)){
    print("ok 25\n");
} else {
    test_error(25,"connect->$host");
}

$dbh3->host eq $host and print("ok 26\n") or print "not ok 26\n";
$dbh3->database eq $dbname and print("ok 27\n") or print "not ok 27\n";


# For what it's worth, we have a tough job for the server here. First
# we define two simple subroutines. The goal of these is to make the
# create table statement independent of what happens on the server
# side. If the table cannot be created we magic increment the
# suggested name and retry. We return the incremented table name. With
# this setting we can run the test script in parallel in many
# processes.

sub create {
    my($db,$tablename,$createexpression) = @_;
    my($query) = "create table $tablename $createexpression";
    my $limit = 0;
    while (! $db->query($query)){
	die "Cannot create table: query [$query] message [$Mysql::db_errstr]\n" if $limit++ > 1000;
	$tablename++;
	$query = "create table $tablename $createexpression";
    }
    $tablename;
}

sub drop { shift->query("drop table $_[0]"); }

# Then we insert some nonsense changing the dbhandle quickly
{
    my $C="AAAA"; 
    my $N=1;
    drop($dbh2,$firsttable);
    $firsttable = create($dbh2,$firsttable,"( name char(40) not null,
            num int, country char(4), mytime real )");

    for (1..5){
	$dbh2->query("insert into $firsttable values
	('".$C++."',".$N++.",'".$C++."',".rand().")") or test_error();
	$dbh3->query("insert into $firsttable values
	('".$C++."',".$N++.",'".$C++."',".rand().")") or test_error();
    }
}

# I haven't shown you yet a cute (and dirty) trick to save memory. As
# ->query returns an object you can reference this object in a single
# chain of -> operators. The statement handle is not preserved, and
# the memory associated with it is cleaned up within a single
# statement. 'Course you never know, which part of the statement
# failed--if something fails.

$dbh2->query("select * from $firsttable")->numrows == 10
    and print("ok 28\n") or print("not ok 28\n");

# Interesting the following test. Creating and dropping of tables via
# two different database handles in quick alteration. There was really
# a version of Mysql that messed up with this

for (1..3){
    drop($dbh2,$firsttable);
    $secondtable = create($dbh3,$secondtable,"( name char(40) not null,
            num int, country char(4), mytime real )");
    drop($dbh2,$secondtable);
    $firsttable = create($dbh3,$firsttable,"( name char(40) not null,
            num int, country char(4), mytime real )");
}
drop($dbh2,$firsttable) and  print("ok 29\n") or print("not ok 29\n");

# A quick check, if the array @{$sth->length} is available and
# correct. See man perlref for an explanation of this kind of
# referencing/dereferencing. Watch out, that we still use an old
# statement handle here. The corresponding table has been overwritten
# quite a few times, but as we are dealing with an in-memeory copy, we
# still have it available

if ("@{$sth->length}" eq "32 32 32"){
    print "ok 30\n";
} else {
    print "not ok 30\n";
}


# Here were two useless tests a while back that didn't please me after
# a while

print "ok 31\n";
print "ok 32\n";

# The following tests show, that NULL fields (introduced with
# Mysql-1.0.6) are handled correctly:

if ($dbh->getserverinfo lt 2) { # Before version 2 we have the "primary key" syntax
    $firsttable = create($dbh,$firsttable,"( she char(14) primary key not null,
	him int, who char(1))") or test_error();
} else {
    $firsttable = create($dbh,$firsttable,"( she char(14) not null,
	him int, who char(1))") or test_error();
    $dbh->query("create unique index she_index on $firsttable ( she )") or test_error();
}

# As you see, we don't insert a value for "him" and "who", so we can
# test the undefinedness

$dbh->query("insert into $firsttable (she) values ('jazz')") or test_error;

$sth = $dbh->query("select * from $firsttable") or test_error;
@row = $sth->fetchrow() or test_error;

# "she" is "jazz", thusly defined

if (defined $row[0]) {
    print "ok 33\n";
} else {
    print "not ok 33\n";
}

# field "him", a character field, should not be defined

if (defined $row[1]) {
    print "not ok 34\n";
} else {
    print "ok 34\n";
}

# field "who", an integer field, should not be defined

if (defined $row[2]) {
    print "not ok 35\n";
} else {
    print "ok 35\n";
}

# So far we have evaluated metadata in scalar context. Let's see,
# if array context works

$i = 35;
foreach (qw/table name type is_not_null is_pri_key length/) {
    my @arr = $sth->$_();
    if (@arr == 3){
	print "ok ", ++$i, "\n";
    } else {
	print "not ok ", ++$i, ": @arr\n";
    }
}
    
# mSQL: A non-select should return TRUE, and if anybody tries to use this
# mSQL: return value as an object reference, we should not core dump
# In mysql a query always return an object!

{
    local($Mysql::QUIET) = 1;
    $sth = $dbh->query("insert into $firsttable values (\047x\047,2,\047y\047)");
    if (!defined($sth->fetchrow))
    {
	print "ok 42\n";
    }
}
    

{
    my($sth_query,$sth_listf,$method);

    # So many people have problems using the ListFields method,
    # so we finally provide a simple example.
    $sth_query = $dbh->query("select * from $firsttable");
    $sth_listf = $dbh->listfields($firsttable);
    $i = 43;
    for $method (qw/name table length type is_not_null is_pri_key/) {
	for (0..$sth_query->numfields -1) {
	    # whatever we do to the one statementhandle, the other one has
	    # to behave exactly the same way
	    if ($sth_query->$method()->[$_] eq $sth_listf->$method()->[$_]) {
		print "ok $i\n" ;
	    } else {
		print "not ok $i\n";
	    }
	    $i++;
	}
    }
    
    # The only difference: the ListFields sth must not have a row associated with
    local($^W) = 0;
    my($got) = $sth_listf->numrows;
    if (!defined $got or $got == 0) {
	print "ok 61\n";
    } else {
	print "not ok 61 - got [$got]\n";
    }
    if ($sth_query->numrows > 0) {
	print "ok 62\n";
    } else {
	print "not ok 62\n";
    }
    
    # Please understand that features that were added later to the module
    # are tested later. Here's a very nice test. Should be easier to
    # understand than the others:
    
    $sth_query->dataseek(0);
    $i = 63;
    while (%hash = $sth_query->fetchhash) {
	
	# fetchhash stuffs the contents of the row directly into a hash
	# instead of a row. We have only two lines to check. Column she
	# has to be either 'jazz' or 'x'.
	if ($hash{she} eq 'jazz' or $hash{she} eq 'x') {
	    print "ok $i\n";
	} else {
	    print "not ok $i\n";
	}
	$i++;
    }
}    

$dbh->query("drop table $firsttable") or test_error;

# Although it is a bad idea to specify constants in lowercase,
# I have to test if it is supported as it has been documented:

if (Mysql::int___type() == INT_TYPE) {
    print "ok 65\n";
} else {
    print "not ok 65\n";
}


# Let's create another table where we inspect if we can insert
# 8 bit characters:

# For mysql, changed character to charactr and char(1) to blob

$query = "create table $firsttable (ascii int, charactr blob)";
$dbh->query($query) or test_error;
my $nchar;
for $nchar (1..255) {
    my $chr = $dbh->quote(chr($nchar));
    $query = qq{
insert into $firsttable values ($nchar, $chr)
    };
    unless ($dbh->query($query)) {
	$query = unctrl($query);
	print "not ok 66 (q[$query] err[$Mysql::db_errstr])\n"; # well, could happen more thn once, but ...
    }
}

sub unctrl {
    my $str = shift;
    $str =~ s/([\000-\037\177])/ '^' . pack('c', ord($1) ^ 64) /eg;
    return $str;
}

$sth = $dbh->query("select * from $firsttable") or test_error;
if ($sth->numrows() == 255){
    print "ok 66\n";
} else {
    print "not ok 66\n";
}
while (%hash = $sth->fetchhash) {
    $hash{charactr} eq chr($hash{ascii}) or print "not ok 67 [char no $hash{ascii}]\n";
}
print "ok 67\n";

$dbh->query("drop table $firsttable") or test_error;

# mSQL up to 1.0.16 had this annoying lost table bug, so I try to
# force our users to upgrade to 1.0.17

{
    my @created = ();
    local($Mysql::QUIET) = 1;

    # create 8 tables
    for (1..8) {
	push @created, create($dbh,$firsttable,q{(foo char(1))});
    }

    # reference all 8 so they are cached
    for (@created) {
	$dbh->listfields($_);
    }

    # reference a non existant table
    my $nonexist = "NONEXIST";
    $nonexist++ while grep /^$nonexist$/, $dbh->listtables;
    $dbh->listfields($nonexist);

    # reference the first table in the cache: 1.0.16 did not know the contents
    if ( $dbh->listfields($created[0])->numfields == 0) {
	my $version = $dbh->getserverinfo;
	print "not ok 68\n";
        print STDERR "Your version $version of the mSQL has a serious bug,
\teither upgrade the server to > 1.0.16 or read the file patch.lost.tables\n";
    } else {
	print "ok 68\n";
    }

    # drop the eight tables
    for (@created) {
	drop($dbh,$_);
    }
}