The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
# Package ODBCTEST
# 
# This package is a common set of routines for the DBD::ODBC tests.
# This is a set of routines to create, drop and test for existance of
# a table for a given DBI database handle (dbh).
#
# This set of routines currently depends greatly upon some ODBC meta-data.
# The meta data required is the driver's native type name for various ODBC/DBI
# SQL types.  For example, SQL_VARCHAR would produce VARCHAR2 under Oracle and TEXT
# under MS-Access.  This uses the function SQLGetTypeInfo.  This is obtained via
# the DBI C<func> method, which is implemented as a call to the driver.  In this case,
# of course, this is the DBD::ODBC.
#
# the SQL_TIMESTAMP may be dubious on many platforms, but SQL_DATE was not supported
# under Oracle, MS SQL Server or Access.  Those are pretty common ones.
#

require 5.004;
{
   package ODBCTEST;

   use DBI qw(:sql_types);

   $VERSION = '0.01';
   $table_name = "PERL_DBD_TEST";

   $longstr = "THIS IS A STRING LONGER THAN 80 CHARS.  THIS SHOULD BE CHECKED FOR TRUNCATION AND COMPARED WITH ITSELF.";
   $longstr2 = $longstr . "  " . $longstr . "  " . $longstr . "  " . $longstr;

    # really dumb work around:
    # MS SQL Server 2000 (MDAC 2.5 and ODBC driver 2000.080.0194.00) have a bug if
    # the column is named C, CA, or CAS and there is a call to SQLDescribeParam...
    # there is an error, referring to a syntax error near keyword 'by'
    # I figured it's just best to rename the columns.
    # changed SQL_BIGINT below to -5, as DBI has removed that constant.
    # ODBC's value is -5.
   %TestFieldInfo = (
		     'COL_A' => [SQL_SMALLINT,-5, SQL_TINYINT, SQL_NUMERIC, SQL_DECIMAL, SQL_FLOAT, SQL_REAL],
		     'COL_B' => [SQL_VARCHAR, SQL_CHAR],
		     'COL_C' => [SQL_LONGVARCHAR, -1],
		     'COL_D' => [SQL_TYPE_TIMESTAMP, SQL_TYPE_DATE, SQL_DATE, SQL_TIMESTAMP ],
		    );

   sub get_type_for_column {
      my $dbh = shift;
      my $column = shift;

      my $type;
      my @row;
      my $sth;
      foreach $type (@{ $TestFieldInfo{$column} }) {
	 $sth = $dbh->func($type, GetTypeInfo);
	    # may not be correct behavior, but get the first compat type
	 if ($sth) {
	    @row = $sth->fetchrow();
	    $sth->finish();
	    last if @row;
	 } else {
		    # warn "Unable to get type for type $type\n";
	 }
      }
      die "Unable to find a suitable test type for field $column"
	    unless @row;
	# warn join(", ",@row);
      return @row;
   }
   sub tab_create {
      my $dbh = shift;
      $dbh->{PrintError} = 0;
      eval {
	 $dbh->do("DROP TABLE $table_name");
      };
      $dbh->{PrintError} = 1;

      # trying to use ODBC to tell us what type of data to use
      my $fields = undef;
      my $f;
      foreach $f (sort keys %TestFieldInfo) {
	    # print "$f: $TestFieldInfo{$f}\n";
	 $fields .= ", " unless !$fields;
	 $fields .= "$f ";
	    # print "-- $fields\n";
	 my @row = get_type_for_column($dbh, $f);
	 $fields .= $row[0];
	 if ($row[5]) {
	    $fields .= "($row[2])"	 if ($row[5] =~ /LENGTH/i);
	    $fields .= "($row[2],0)" if ($row[5] =~ /PRECISION,SCALE/i);
	 }
	 if ($f eq 'COL_A') {
	    $fields .= " NOT NULL PRIMARY KEY ";
	 }
	    # print "-- $fields\n";
      }
      print "Using fields: $fields\n";
      $dbh->do("CREATE TABLE $table_name ($fields)");
   }


   sub tab_delete {
      my $dbh = shift;
      $dbh->do("DELETE FROM $table_name");
   }

   sub tab_exists {
      my $dbh = shift;
      my (@rows, @row, $rc);

      $rc = -1;

      unless ($sth = $dbh->table_info()) {
	 print "Can't list tables: $DBI::errstr\n";
	 return -1;
      }
	# TABLE_QUALIFIER,TABLE_OWNER,TABLE_NAME,TABLE_TYPE,REMARKS
      while ($row = $sth->fetchrow_hashref()) {
	    # XXX not fully true.  The "owner" could be different.  Need to check!
	    # In Oracle, testing $user against $row[1] works, but does NOT in SQL Server.
	    # SQL server returns the device and something else I haven't quite taken the time
	    # to figure it out, since I'm not a SQL server expert.  Anyone out there?
	    # (mine returns "dbo" for the owner on ALL my tables.  This is obviously something
	    # significant for SQL Server...one of these days I'll dig...
	 if (($table_name eq uc($row->{TABLE_NAME}))) {
		# and (uc($user) eq uc($row[1]))) 
		# qeDBF driver returns null for TABLE_OWNER
	    my $owner = $row->{TABLE_OWNER} || '(unknown owner)';
	    print "$owner.$row->{TABLE_NAME}\n";
	    $rc = 1;
	    last;
	 }
      }
      $sth->finish();
      $rc;
   }

   #
   # show various ways of inserting data without binding parameters.
   # Note, these are not necessarily GOOD ways to
   # show this...
   #

   @tab_insert_values = ( 
			 [1, 'foo', 'foo varchar', "{d '1998-05-11'}", "{ts '1998-05-11 00:00:00'}"],
			 [2, 'bar', 'bar varchar', "{d '1998-05-12'}", "{ts '1998-05-12 00:00:00'}"],
			 [3, "bletch", "bletch varchar", "{d '1998-05-10'}", "{ts '1998-05-10 00:00:00'}"],
			 [4, "80char", $longstr, "{d '1998-05-13'}", "{ts '1998-05-13 12:00:00'}"],
			 [5, "gt250char", $longstr2, "{d '1998-05-14'}", "{ts '1998-05-14 00:00:00'}"],
			);
			   
   sub tab_insert {
      my $dbh = shift;

       # qeDBF needs a space after the table name!
      foreach (@tab_insert_values) {
      
	 @row = ODBCTEST::get_type_for_column($dbh, 'COL_D');
	 # print "TYPE FOUND = $row[1]\n";
	 if (!$dbh->do("INSERT INTO $table_name (COL_A, COL_B, COL_C, COL_D) VALUES ("
		 . join(", ", $_->[0],
			$dbh->quote($_->[1]),
			$dbh->quote($_->[2]), 
			$_->[isDateType($row[1]) ? 3 : 4]). ")")) {
	    return 0;
	 }
      }
      1;
   }

   sub isDateType($) {
      my $type = shift;
      if ($type == SQL_DATE  || $type == SQL_TYPE_DATE) {
	 return 1;
      } else {
	 return 0;
      }
   }

   sub tab_insert_bind {
      my $dbh = shift;
      my $dref = shift;
      my $handle_column_type = shift;
      my @data = @{$dref};

      my $sth = $dbh->prepare("INSERT INTO $table_name (COL_A, COL_B, COL_C, COL_D) VALUES (?, ?, ?, ?)");
      unless ($sth) {
	 warn $DBI::errstr;
	 return 0;
      }
      # $sth->{PrintError} = 1;
      foreach (@data) {
	 my @row;
	 if ($handle_column_type) {
	    @row = ODBCTEST::get_type_for_column($dbh, 'COL_A');
	    print "Binding the value: $_->[0] type = $row[1]\n";
	    $sth->bind_param(1, $_->[0], { TYPE => $row[1] });
	 } else {
	    $sth->bind_param(1, $_->[0]);
	 }
	 if ($handle_column_type) {
	    @row = ODBCTEST::get_type_for_column($dbh, 'COL_B');
	    $sth->bind_param(2, $_->[1], { TYPE => $row[1] });
	 } else {
	    $sth->bind_param(2, $_->[1]);
	 }
	 if ($handle_column_type) {
	    @row = ODBCTEST::get_type_for_column($dbh, 'COL_C');
	    $sth->bind_param(3, $_->[2], { TYPE => $row[1] });
	 } else {
	    $sth->bind_param(3, $_->[2]);
	 }
	 
	 # print "SQL_DATE = ", SQL_DATE, " SQL_TIMESTAMP = ", SQL_TIMESTAMP, "\n";
	 @row = ODBCTEST::get_type_for_column($dbh, 'COL_D');
	 print "TYPE FOUND = $row[1]\n";
	 # if ($row[1] == SQL_TYPE_TIMESTAMP) {
	 #   $row[1] = SQL_TIMESTAMP;
	 #}
	 # print "Binding the date value: \"$_->[$row[1] == SQL_DATE ? 3 : 4]\"\n";
	 if ($handle_column_type) {
	    $sth->bind_param(4, $_->[isDateType($row[1]) ? 3 : 4], { TYPE => $row[1] });
	 } else {
	    # sigh, couldn't figure out how to get rid of the warning nicely,
	    # so I turned it off!!!  Now, I have to turn it back on due
	    # to  problems in other perl versions.
	    $sth->bind_param(4, $_->[isDateType($row[1]) ? 3 : 4]);
	 }
	 return 0 unless $sth->execute;
      }
      1;
   }
   1;
}