Here is the fix:
sub execute and sub bind_param in DBD\ADO.pm were modified to
handle undef in parameter values. Like below.
if(defined($val)){
......
......
}else{
$i->{Value} = Variant(VT_NULL);
}
Attached file contains new code for these 2 subs in full.
Thanks
On Mon, 30 Jun 2003 09:18:50 -0400 (EDT)
Yimin Zheng wrote:
>hi there,
>
>I am having trouble inserting null value into table using DBD:ADO
>and placeholders. I tried bind_param, bind_param_array and
execute_array,
>none worked. I tried MS Access and MS SQL server for data sources,
>none worked. There is no problem if I use dbi:ODBC instead
>to access the same backend database. So I think the problem
>must be in DBD:ADO.
>Below is the code that I tested and failed. Am I the only one
>with this problem?
>
>Thank you
>
>---------------------
>code 1, using bind_param:
>$sth = $dbh->prepare("INSERT INTO tbl_parameter VALUES (?, ?, ?)");
>$sth->bind_param(1, "name");
>$sth->bind_param(2, "value");
>$sth->bind_param(3, undef);
>$sth->execute;
>
>result:
>Can't use string ("0") as an ARRAY ref while "strict refs" in
>use at C:/Perl/site/lib/DBD/ADO.pm line 1811.
>Connection open, destroy at test004.pl line 0
>
>---------------------
>code 2, using bind_param_array and execute_array:
>$sth = $dbh->prepare("INSERT INTO tbl_parameter VALUES (?, ?, ?)");
>$sth->bind_param_array(1, [undef]);
>$sth->bind_param_array(2, ["value"]);
>$sth->bind_param_array(3, ["description"]);
>$tuples = $sth->execute_array({ ArrayTupleStatus => [EMAIL PROTECTED]
>});
>
>result:
>DBD::ADO::st execute_array failed: Can't execute statement 'INSERT
>INTO tbl_parameter VALUES (?, ?, ?)':
>Lasterror: -2146824580: OLE exception from "ADODB.Command":
>
>Parameter object is improperly defined. Inconsistent or incomplete
>information was provided.
>
>Win32::OLE(0.1502) error 0x800a0e7c
> in METHOD/PROPERTYGET "Execute" at test004.pl line 56.
>
>---------------------
>code 3, using execute_array:
>$sth = $dbh->prepare("INSERT INTO tbl_parameter VALUES (?, ?, ?)");
>$tuples = $sth->execute_array({ ArrayTupleStatus => [EMAIL PROTECTED]
>}, [undef], ["value"], ["description"]);
>
>result:
>DBD::ADO::st execute_array failed: Can't execute statement 'INSERT
>INTO tbl_parameter VALUES (?, ?, ?)':
>Lasterror: -2146824580: OLE exception from "ADODB.Command":
>
>Parameter object is improperly defined. Inconsistent or incomplete
>information was provided.
>
>Win32::OLE(0.1502) error 0x800a0e7c
> in METHOD/PROPERTYGET "Execute" at test004.pl line 59.
>
>
>
>--------------------------------------------------------------------------
>Global Internet phone calls, voicemail, fax, e-mail and instant
messaging.
>Sign-up today at http://www.hotvoice.com
>
>
--------------------------------------------------------------------------
Global Internet phone calls, voicemail, fax, e-mail and instant messaging.
Sign-up today at http://www.hotvoice.com sub bind_param {
my ($sth, $pNum, $val, $attr) = @_;
my $conn = $sth->{ado_conn};
my $comm = $sth->{ado_comm};
my $ado_consts = $sth->{ado_dbh}->{ado_consts};
my $param_cnt = $sth->FETCH( 'NUM_OF_PARAMS' );
return DBI::set_err($sth, $DBD::ADO::err,
"Bind Parameters called with no parameters defined!")
unless $param_cnt;
return DBI::set_err($sth, $DBD::ADO::err,
"Bind Parameter $pNum outside current range of
$param_cnt.")
if ($pNum > $param_cnt or $pNum < 1);
# Get the data type
my $type = (ref $attr) ? $attr->{TYPE}: $attr;
# Convert from ODBC to ADO type
my $aType = &_convert_type($type);
my $pd;
my $params = $sth->{ado_params};
$params->[$pNum-1] = $val;
my $p = $comm->Parameters;
# Determine if the Parameter is defined.
my $i = $p->Item( $pNum -1 );
if(defined($val)){
if ($i->{Type} == $ado_consts->{adVarBinary} or
$i->{Type} ==
$ado_consts->{adLongVarBinary}
) {
# Deal with an image request.
my $sz = length $val;
#my $pic2 =
Variant(VT_UI1|VT_ARRAY,$i->{Size});
my $pic = Variant(VT_UI1|VT_ARRAY,$sz + 10);
$pic->Put($val);
$i->{Value} = $pic;
$sth->trace_msg( "->(VarBinary) : ". $i->Size.
" ". $i->Type. "\n");
} else {
$i->{Size} = $val? length $val: $aType->[2];
$i->{Value} = $val if $val;
$sth->trace_msg( "->(default) : ". $i->Size. "
". $i->Type. "\n");
}
}else{
$i->{Value} = Variant(VT_NULL);
}
return 1;
}
sub execute {
my ($sth, @bind_values) = @_;
my $comm = $sth->{ado_comm};
my $conn = $sth->{ado_conn};
my $ado_consts = $sth->{ado_dbh}->{ado_consts};
my $sql = $sth->FETCH("Statement");
$sth->trace_msg("-> execute state handler\n");
# If a record set is currently defined,
# release the set.
my $ors = $sth->{ado_rowset};
if (defined $ors) {
$ors->Close () if $ors and
$ors->State & $ado_consts->{adStateOpen};
$sth->STORE(ado_rowset => undef);
$ors = undef;
}
#
# If the application is excepting arguments, then
# process them here.
#
my $lastError;
my $rs;
my $p = $comm->Parameters;
$lastError = DBD::ADO::errors($conn);
return DBI::set_err($sth, $DBD::ADO::err,
"Execute Parameters failed 'ADODB.Command':
$lastError")
if $lastError and $DBD::ADO::err ne NOT_SUPPORTED;
my $not_supported = ( $DBD::ADO::err eq NOT_SUPPORTED ) || 0;
$sth->trace_msg( " -> Not Supported flag: $not_supported\n" );
my $parm_cnt = 0;
# Need to test if we can access the parameter attributes.
{
# Turn the OLE Warning Off for this test.
local ($Win32::OLE::Warn);
$Win32::OLE::Warn = 0;
$parm_cnt = $p->{Count};
$lastError = DBD::ADO::errors($conn);
$not_supported = ( $DBD::ADO::err eq EXCEPTION_OCC )
|| 0;
}
$sth->trace_msg( " -> Is the Parameter Object Supported? " .
($not_supported ? 'No' : 'Yes') . "\n" );
# Remember if the provider errored with a "not supported"
message.
return DBI::set_err( $sth, $DBD::ADO::err,
"Bind params passed without place holders")
if (@bind_values and $p->{Count} == 0);
my $x = 0;
# Convert the parameters as needed.
for (@bind_values) {
my $i = $p->Item($x);
# Fix from Jacqui Caren <[EMAIL PROTECTED]>,
if(defined($_)){
if ($i->{Type} == $ado_consts->{adVarBinary} or
$i->{Type} ==
$ado_consts->{adLongVarBinary}
) {
# Deal with an image request.
my $sz = length $_;
#my $pic =
Variant(VT_UI1|VT_ARRAY,$i->{Size});
my $pic = Variant(VT_UI1|VT_ARRAY,$sz
+ 10);
$pic->Put($_);
$i->{Value} = $pic;
} else {
$i->{Size} = length $_;
$i->{Value} = $_;
}
}else{
$i->{Value} = Variant(VT_NULL);
}
$sth->trace_msg("-> Bind parameter (execute): " .
$i->Type . "\n");
$x++;
}
$x = 0;
# If the provider errored with not_supported above in the
Parameters
# methods, do not attempt to display anything about the
object. If we
# it triggers warning message.
unless($not_supported) {
$sth->trace_msg( "-> Parameter count: " . $p->{Count}
. "\n");
while( $x < $p->{Count} ) {
my $params = $sth->{ado_params};
$sth->trace_msg( "-> Parameter $x: " .
($p->Item($x)->{Value}|| 'undef') . "\n");
$sth->trace_msg( "-> Parameter $x: " .
($params->[$x]||'undef') . "\n");
$x++;
}
}
# At this point a command is ready to execute. To allow for
different
# type of cursors, I need to create a recordset object.
# Return the affected number to rows.
my $rows = Variant->new($VT_I4_BYREF, 0);
# However, a RecordSet Open does not return affected rows. So
I need to
# determine if a recordset open is needed, or a command
execute.
# print "usecmd ", exists $sth->{ado_usecmd},
defined $sth->{ado_usecmd}, "\n";
# print "CursorType ", exists
$sth->{ado_attribs}->{CursorType}, defined $sth->{ado_attribs}->{CursorType}, "\n";
# print "cursortype ", exists $sth->{ado_cursortype},
defined $sth->{ado_cursortype}, "\n";
# print "users ", exists $sth->{ado_users},
defined $sth->{ado_users}, "\n";
my $UseRecordSet = (
not (exists $sth->{ado_usecmd}
and defined $sth->{ado_usecmd})
&& ((exists $sth->{ado_attribs}->{CursorType} and
defined $sth->{ado_attribs}->{CursorType})
|| (exists $sth->{ado_cursortype} and defined
$sth->{ado_cursortype})
|| (exists $sth->{ado_users} and
defined $sth->{ado_users}))
);
if ( $UseRecordSet ) {
$rs = Win32::OLE->new('ADODB.RecordSet');
$lastError = Win32::OLE->LastError;
return $sth->DBI::set_err(1,
"Can't create 'object ADODB.RecordSet':
$lastError")
if $lastError;
# Determine the the CursorType to use. The default is
adOpenForwardOnly.
my $cursortype = $ado_consts->{adOpenForwardOnly};
if ( exists $sth->{ado_attribs}->{CursorType} ) {
my $type = $sth->{ado_attribs}->{CursorType};
if (exists $ado_consts->{$type}) {
$sth->trace_msg( " -> Changing the
cursor type to $type\n" );
$cursortype = $ado_consts->{$type};
} else {
warn "Attempting to use an invalid
CursorType: $type : using default adOpenForwardOnly";
}
}
# Call to clear any previous error messages.
$lastError = DBD::ADO::errors($conn);
$sth->trace_msg( " Open record set using cursor type:
$cursortype\n" );
$rs->Open( $comm, undef, $cursortype );
# Execute the statement, get a recordset in return.
# $rs = $comm->Execute($rows);
$lastError = DBD::ADO::errors($conn);
return $sth->DBI::set_err( $DBD::ADO::err,
"Can't execute statement '$sql':
$lastError")
if $DBD::ADO::err;
} else {
# Execute the command.
# Execute the statement, get a recordset in return.
$rs = $comm->Execute($rows);
$lastError = DBD::ADO::errors($conn);
return $sth->DBI::set_err( $DBD::ADO::err,
"Can't execute statement '$sql':
$lastError")
if $DBD::ADO::err;
}
$sth->{ado_fields} = my $ado_fields = [
Win32::OLE::in($rs->Fields) ];
my $num_of_fields = @$ado_fields;
if ($num_of_fields == 0) { # assume non-select statement
# If the AutoCommit is on, Commit current
transaction.
$conn->CommitTrans
if $sth->{ado_dbh}->{AutoCommit}
and
$sth->{ado_dbh}->{ado_provider_support_auto_commit};
$lastError = DBD::ADO::errors($conn);
return DBI::set_err( $sth, $DBD::ADO::err,
"Execute: Commit failed:
$lastError")
if $lastError;
# Determine the effected row count?
my $c = ($rows->Value == 0 ? qq{0E0} :
$rows->Value);
$sth->STORE('rows', $c);
$sth->trace_msg("<- executed state handler (no
recordset)\n");
# Clean up the record set that isn't used.
if (defined $rs and (ref $rs) =~ /Win32::OLE/)
{
$rs->Close () if $rs and
$rs->State &
$ado_consts->{adStateOpen};
}
$rs = undef;
return ( $c );
}
$sth->STORE( ado_rowset => $rs );
# Current setting of RowsInCache?
my $rowcache = $sth->FETCH( 'RowCacheSize' );
if ( defined $rowcache and $rowcache > 0 ) {
my $currowcache = $rs->CacheSize( );
$sth->trace_msg( " changing the CacheSize
using RowCacheSize: $rowcache" );
$rs->CacheSize( $rowcache ) unless $rowcache
== $currowcache;
$lastError = DBD::ADO::errors($conn);
return $sth->DBI::set_err( $DBD::ADO::err,
" Unable to change CacheSize
to RowCacheSize : $rowcache : $lastError")
if $DBD::ADO::err;
warn "Changed CacheSize\n";
}
my $nof = $sth->FETCH('NUM_OF_FIELDS');
$sth->STORE(Active => 1);
$sth->STORE('NUM_OF_FIELDS' => $num_of_fields)
unless ($nof == $num_of_fields);
$sth->STORE( NAME => [ map { $_->Name }
@$ado_fields ] );
$sth->STORE( TYPE => [ map {
DBD::ADO::db::convert_ado_to_odbc($sth, $_->Type)
} @$ado_fields ] );
$sth->STORE( PRECISION => [ map { $_->Precision } @$ado_fields ] );
$sth->STORE( SCALE => [
map { $_->NumericScale } @$ado_fields ] );
$sth->STORE( NULLABLE =>
[
map { $_->Attributes & $ado_consts->{adFldMayBeNull}?
1 : 0 }
@$ado_fields
]
);
$sth->STORE( ado_type => [ map { $_->Type } @$ado_fields ] );
# print "May Defer"
# , join( ", "
# , map { $_->Attributes & $ado_consts->{adFldMayDefer}?
1 : 0 }
# @$ado_fields ), "\n";
# print "Is Long"
# , join( ", "
# , map { $_->Attributes & $ado_consts->{adFldLong}? 1 :
0 }
# @$ado_fields ), "\n";
$sth->STORE( CursorName => undef);
$sth->STORE( Statement => $rs->Source);
$sth->STORE( RowsInCache => $rs->CacheSize);
$sth->STORE( rows =>
$rs->RecordCount );
# We need to return a true value for a successful select
# -1 means total row count unavailable
$sth->trace_msg("<- executed state handler\n");
return $rs->RecordCount;
}