{ package DBD::ADO; use strict; use DBI(); use Win32::OLE(); use vars qw($VERSION $drh); $VERSION = '2.99'; $drh = undef; sub driver { return $drh if $drh; my ( $class, $attr ) = @_; $drh = DBI::_new_drh( $class . '::dr', { Name => 'ADO' , Version => $VERSION , Attribution => 'DBD ADO for Win32 by Tim Bunce, Phlip, Thomas Lowery and Steffen Goeldner' } ); if ( $DBI::VERSION >= 1.37 ) { DBD::ADO::db->install_method('ado_open_schema'); } $drh->STORE('LongReadLen', 2147483647 ); return $drh; } sub CLONE { undef $drh; } sub errors { my $h = shift; my $Cxn = $h->{ado_conn}; my $MaxErrors = $h->{ado_max_errors} || 50; my @Err = (); my $lastError = Win32::OLE->LastError; if ( $lastError ) { $h->{ado_errcum} = $h->{ado_err} = 0+$lastError; push @Err,"\n Last error : $h->{ado_err}\n\n$lastError"; } else { $h->{ado_errcum} = $h->{ado_err} = 0; } $h->{ado_state} = ''; if ( ref $Cxn ) { my $Errors = $Cxn->Errors; if ( $Errors ) { my $Count = $Errors->Count; for ( my $i = 1; $i <= $Count; $i++ ) { if ( $i > $MaxErrors ) { push @Err,"\n ... (too many errors: $Count)"; $i = $Count; } my $err = $Errors->Item( $i - 1 ); push @Err,''; push @Err, sprintf "%19s : %s", $_, $err->$_ ||'' for qw( Description HelpContext HelpFile NativeError Number Source SQLState); push @Err,' '; $h->{ado_errcum} |= $err->Number; $h->{ado_state} = $err->SQLState ||''; } $Errors->Clear; } } join "\n", @Err; } sub Failed { my $h = shift; my $lastError = DBD::ADO::errors( $h ) or return 0; my ( $package, $filename, $line ) = caller; my $s = shift() . "\n" . "\n Package : $package" . "\n Filename : $filename" . "\n Line : $line" ; $h->{ado_err} = 0 unless $h->{ado_errcum} & 1 << 31; # oledberr.h my $state = $h->{ado_state} if length $h->{ado_state} == 5; $h->set_err( $h->{ado_err}, $s . $lastError, $state ); return 1; } } { package DBD::ADO::dr; # ====== DRIVER ====== use strict; use DBI(); use Win32::OLE(); $DBD::ADO::dr::imp_data_size = 0; sub data_sources { my ( $drh, $attr ) = @_; my @list = (); $drh->{ado_data_sources} ||= eval { require Local::DBD::ADO::DSN } || []; $drh->trace_msg(" !! $@", 7 ) if $@; for my $h ( @{$drh->{ado_data_sources}} ) { my @a = map "$_=$h->{$_}", sort keys %$h; push @list,'dbi:ADO:' . join(';', @a ); } return @list; } sub connect { my ( $drh, $dsn, $user, $auth, $attr ) = @_; local $Win32::OLE::Warn = 0; my $conn = Win32::OLE->new('ADODB.Connection'); return if DBD::ADO::Failed( $drh,"Can't create 'ADODB.Connection'"); if ( exists $attr->{ado_ConnectionTimeout} ) { $conn->{ConnectionTimeout} = $attr->{ado_ConnectionTimeout}; return if DBD::ADO::Failed( $drh,"Can't set ConnectionTimeout"); } if ( exists $attr->{ado_Mode} ) { $conn->{Mode} = $attr->{ado_Mode}; return if DBD::ADO::Failed( $drh,"Can't set Mode"); } my ( $outer, $dbh ) = DBI::_new_dbh( $drh, { Name => $dsn } ); $dbh->{AutoCommit} = 1; # Initially, ADO is in auto-commit mode $dbh->{ado_conn} = $conn; $dbh->{ado_max_errors} = 50; $dbh->{ado_ti_ver} = 2; # TypeInfo version # ODBC rule: NULL is not the same as an empty password ... $auth = '' unless defined $auth; my @dsn; for my $s ( split /;/, $dsn ) { my ( $k, $v ) = split /=/, $s, 2; if ( defined $conn->{$k} ) { $conn->{$k} = $v; next; } push @dsn, $s; } my $ConnectionString = join ';', @dsn; $drh->trace_msg(" -- ConnectionString: $ConnectionString\n", 5 ); $conn->Open( $ConnectionString, $user, $auth ); return if DBD::ADO::Failed( $drh,"Can't Open Connection '$dsn'"); # Determine transaction support eval { $dbh->{ado_txn_capable} = $conn->{Properties}{'Transaction DDL'}{Value}; }; if ( $@ ) { $dbh->{ado_txn_capable} = 0; my $lastError = DBD::ADO::errors( $dbh ); $drh->trace_msg(" !! Can't determine transaction support: $lastError\n", 5 ); } $drh->trace_msg(" -- Transaction support: $dbh->{ado_txn_capable}\n", 5 ); $dbh->STORE('Warn' , 0 ); $dbh->STORE('Active', 1 ); return $outer; } } # ====== DRIVER ====== { package DBD::ADO::db; # ====== DATABASE ====== use strict; use DBI(); use Win32::OLE(); use Win32::OLE::Variant(); use DBD::ADO::TypeInfo(); use DBD::ADO::Const(); use Carp(); $DBD::ADO::db::imp_data_size = 0; my $Enums = DBD::ADO::Const->Enums; my $ado_schematables = [ qw( TABLE_CAT TABLE_SCHEM TABLE_NAME TABLE_TYPE REMARKS TABLE_GUID TABLE_PROPID DATE_CREATED DATE_MODIFIED ) ]; my $ado_dbi_schematables = [ qw( TABLE_CAT TABLE_SCHEM TABLE_NAME TABLE_TYPE REMARKS ) ]; my $sch_dbi_to_ado = { TABLE_CAT => 'TABLE_CATALOG' , TABLE_SCHEM => 'TABLE_SCHEMA' , TABLE_NAME => 'TABLE_NAME' , TABLE_TYPE => 'TABLE_TYPE' , REMARKS => 'DESCRIPTION' , TABLE_GUID => 'TABLE_GUID' , TABLE_PROPID => 'TABLE_PROPID' , DATE_CREATED => 'DATE_CREATED' , DATE_MODIFIED => 'DATE_MODIFIED' }; sub ping { my ( $dbh ) = @_; my $conn = $dbh->{ado_conn}; defined $conn && $conn->State & $Enums->{ObjectStateEnum}{adStateOpen}; } sub disconnect { my ( $dbh ) = @_; my $conn = $dbh->{ado_conn}; if ( defined $conn ) { local $Win32::OLE::Warn = 0; my $State = $conn->State || 0; $dbh->trace_msg(" -- State: $State\n", 5 ); if ( $State & $Enums->{ObjectStateEnum}{adStateOpen} ) { # Change the connection attribute so Commit/Rollback # does not start another transaction. $conn->{Attributes} = 0; my $lastError = DBD::ADO::errors( $dbh ); return $dbh->set_err( -925,"Can't set CommitRetaining: $lastError") if $lastError && $lastError !~ m/-2147168242/; $dbh->trace_msg(' -- Modified ADO Connection Attributes: ' . $conn->{Attributes} . "\n", 5 ); $dbh->rollback if !$dbh->{AutoCommit} && $dbh->{ado_txn_capable}; $conn->Close; } $dbh->{ado_conn} = undef; } $dbh->SUPER::STORE('Active', 0 ); return 1; } sub commit { my ( $dbh ) = @_; my $conn = $dbh->{ado_conn}; return Carp::carp "Commit ineffective when AutoCommit is on\n" if $dbh->{AutoCommit} && $dbh->FETCH('Warn'); return Carp::carp 'Transactions are not supported' unless $dbh->{ado_txn_capable}; if ( $dbh->FETCH('BegunWork') ) { $dbh->{AutoCommit} = 1; $dbh->SUPER::STORE('BegunWork', 0 ); $conn->{Attributes} = 0; return if DBD::ADO::Failed( $dbh,"Can't set CommitRetaining"); } if ( defined $conn && $conn->State & $Enums->{ObjectStateEnum}{adStateOpen} ) { $conn->CommitTrans; return if DBD::ADO::Failed( $dbh,"Can't Commit transaction"); } return 1; } sub rollback { my ( $dbh ) = @_; my $conn = $dbh->{ado_conn}; return Carp::carp "Rollback ineffective when AutoCommit is on\n" if $dbh->{AutoCommit} && $dbh->FETCH('Warn'); return Carp::carp 'Transactions are not supported' unless $dbh->{ado_txn_capable}; if ( $dbh->FETCH('BegunWork') ) { $dbh->{AutoCommit} = 1; $dbh->SUPER::STORE('BegunWork', 0 ); $conn->{Attributes} = 0; return if DBD::ADO::Failed( $dbh,"Can't set CommitRetaining"); } if ( defined $conn && $conn->State & $Enums->{ObjectStateEnum}{adStateOpen} ) { $conn->RollbackTrans; return if DBD::ADO::Failed( $dbh,"Can't Rollback transaction"); } return 1; } # The create parm methods builds a usable type statement for constructing # tables. # XXX This method may not stay ... sub create_parm { my ( $dbh, $type ) = @_; my $field = undef; if ( $type ) { $field = $type->{TYPE_NAME}; if ( defined $type->{CREATE_PARAMS} ) { $field .= '(' . $type->{COLUMN_SIZE} . ')' if $type->{CREATE_PARAMS} =~ /LENGTH/i; $field .= '(' . $type->{COLUMN_SIZE} . ', 0)' if $type->{CREATE_PARAMS} =~ /PRECISION,SCALE/i; } } return $field; } sub prepare { my ( $dbh, $statement, $attr ) = @_; my $conn = $dbh->{ado_conn}; my $comm = Win32::OLE->new('ADODB.Command'); return if DBD::ADO::Failed( $dbh,"Can't create 'ADODB.Command'"); $comm->{ActiveConnection} = $conn; return if DBD::ADO::Failed( $dbh,"Can't set ActiveConnection"); $comm->{CommandText} = $statement; return if DBD::ADO::Failed( $dbh,"Can't set CommandText"); my $ct = $attr->{CommandType} ? $attr->{CommandType} : 'adCmdText'; $comm->{CommandType} = $Enums->{CommandTypeEnum}{$ct}; return if DBD::ADO::Failed( $dbh,"Can't set CommandType"); $comm->{CommandTimeout} = defined $attr->{ado_commandtimeout} ? $attr->{ado_commandtimeout} : $conn->{CommandTimeout}; return if DBD::ADO::Failed( $dbh,"Can't set CommandTimeout"); my ( $outer, $sth ) = DBI::_new_sth( $dbh, { Statement => $statement } ); $sth->{ado_cachesize} = $dbh->{ado_cachesize}; $sth->{ado_comm} = $comm; $sth->{ado_conn} = $conn; $sth->{ado_cursortype} = $dbh->{ado_cursortype} || $attr->{CursorType}; $sth->{ado_fields} = undef; $sth->{ado_max_errors} = $dbh->{ado_max_errors}; $sth->{ado_refresh} = 1; $sth->{ado_rownum} = -1; $sth->{ado_rows} = -1; $sth->{ado_rowset} = undef; $sth->{ado_type} = undef; $sth->{ado_usecmd} = undef; $sth->{ado_users} = undef; $sth->{ado_executeoption} = 0; # Set overrides for and attributes. for my $key ( grep { /^ado_/ } keys %$attr ) { next if $key eq 'ado_commandtimeout'; $sth->trace_msg(" -- Attribute: $key => $attr->{$key}\n", 5 ); if ( exists $sth->{$key} ) { $sth->{$key} = $attr->{$key}; } else { warn "Unknown attribute $key\n"; } } my $Cnt; if ( $sth->{ado_refresh} == 1 ) { # Refresh() is - among other things - useful to detect syntax errors. # The eval block is used because Refresh() may not be supported (but # no such case is known). # Buggy drivers, e.g. FoxPro, may leave the Parameters collection # empty, without returning an error. Then _refresh() is deferred until # bind_param() is called. eval { local $Win32::OLE::Warn = 0; $comm->Parameters->Refresh; $Cnt = $comm->Parameters->Count; }; my $lastError = DBD::ADO::errors( $dbh ); if ( $lastError ) { $dbh->trace_msg(" !! Refresh error: $lastError\n", 5 ); $sth->{ado_refresh} = 2; } } if ( $sth->{ado_refresh} == 2 ) { $Cnt = DBD::ADO::st::_refresh( $sth ); } # LRB if ( $sth->{ado_executeoption} && $sth->{ado_executeoption} == $Enums->{ExecuteOptionEnum}{adExecuteStream}) { my $sResponseStream = Win32::OLE->new('ADODB.Stream'); return if DBD::ADO::Failed($dbh, "Can't create 'ADODB.Stream'"); $sResponseStream->Open(); return if DBD::ADO::Failed($dbh, "Can't open 'ADODB.Stream'"); my $vObj = Win32::OLE::Variant->new(Win32::OLE::Variant::VT_VARIANT()|Win32::OLE::Variant::VT_BYREF(), $sResponseStream); return if DBD::ADO::Failed($dbh, "Can't create Variant for 'ADODB.Stream'"); $comm->{Properties}{'Output Stream'}{Value} = $vObj; $sth->{ado_responsestream} = $sResponseStream; } if ( $Cnt ) { # Describe the Parameters: for my $p ( Win32::OLE::in( $comm->Parameters ) ) { my @p = map "$_ => $p->{$_}", qw(Name Type Direction Size); $dbh->trace_msg(" -- Parameter: @p\n", 5 ); } $sth->STORE('NUM_OF_PARAMS', $Cnt ); } $comm->{Prepared} = 1; return if DBD::ADO::Failed( $dbh,"Can't set Prepared"); return $outer; } # Creates a Statement handle from a row set. sub _rs_sth_prepare { my ( $dbh, $rs, $sth1 ) = @_; $dbh->trace_msg(" -> _rs_sth_prepare: Create statement handle from RecordSet\n", 3 ); my $conn = $dbh->{ado_conn}; my @Fields = Win32::OLE::in( $rs->Fields ); my ( $outer, $sth ) = $sth1 ? ( undef, $sth1 ) : DBI::_new_sth( $dbh, { Statement => $rs->Source } ); $sth->{ado_comm} = $conn; # XXX $sth->{ado_conn} = $conn; $sth->{ado_fields} = \@Fields; $sth->{ado_max_errors} = $dbh->{ado_max_errors}; $sth->{ado_refresh} = 0; $sth->{ado_rownum} = 0; $sth->{ado_rows} = -1; $sth->{ado_rowset} = $rs; $sth->{ado_type} = [ map { $_->Type } @Fields ]; $sth->{NAME} = [ map { $_->Name } @Fields ]; $sth->{TYPE} = [ map { scalar DBD::ADO::TypeInfo::ado2dbi( $_->Type ) } @Fields ]; $sth->{PRECISION} = [ map { $_->Precision } @Fields ]; $sth->{SCALE} = [ map { $_->NumericScale } @Fields ]; $sth->{NULLABLE} = [ map { $_->Attributes & $Enums->{FieldAttributeEnum}{adFldMayBeNull} ? 1 : 0 } @Fields ]; $sth->STORE('NUM_OF_FIELDS', scalar @Fields ); $sth->STORE('Active', 1 ); $dbh->trace_msg(" <- _rs_sth_prepare: Create statement handle from RecordSet\n", 3 ); return $outer; } sub get_info { my ( $dbh, $info_type ) = @_; $info_type = int $info_type; require DBD::ADO::GetInfo; return $dbh->{ado_conn}->Properties->{$DBD::ADO::GetInfo::odbc2ado{$info_type}}{Value} if exists $DBD::ADO::GetInfo::odbc2ado{$info_type}; my $v = $DBD::ADO::GetInfo::info{$info_type}; if ( ref $v eq 'CODE') { my $get_info_cache = $dbh->{dbd_get_info_cache} ||= {}; return $get_info_cache->{$info_type} if exists $get_info_cache->{$info_type}; $v = $v->( $dbh ); return $$v if ref $v eq 'SCALAR'; # don't cache! $get_info_cache->{$info_type} = $v; } return $v; } sub ado_schema_dbinfo_literal { my ( $dbh, $literal_name ) = @_; my $cache = $dbh->{ado_schema_dbinfo_literal_cache}; unless ( defined $cache ) { $dbh->trace_msg(" -- ado_schema_dbinfo_literal: filling cache\n", 5 ); $cache = $dbh->{ado_schema_dbinfo_literal_cache} = {}; my $sth = $dbh->func('adSchemaDBInfoLiterals','OpenSchema'); while ( my $row = $sth->fetch ) { $cache->{$row->[0]} = [ @$row ]; } } my $row = $cache->{$literal_name}; return $row->[1] unless wantarray; # literal value return @$row; } sub table_info { my ( $dbh, $attr ) = @_; $attr = { TABLE_CAT => $_[1] , TABLE_SCHEM => $_[2] , TABLE_NAME => $_[3] , TABLE_TYPE => $_[4] , ref $_[5] eq 'HASH' ? %{$_[5]} : () } unless ref $attr eq 'HASH'; my @Rows; my $conn = $dbh->{ado_conn}; $attr->{ado_columns} = $attr->{ADO_Columns} if exists $attr->{ADO_Columns} && !exists $attr->{ado_columns}; $attr->{ado_filter} = $attr->{Filter} if exists $attr->{Filter} && !exists $attr->{ado_filter}; $attr->{ado_trim_catalog} = $attr->{Trim_Catalog} if exists $attr->{Trim_Catalog} && !exists $attr->{ado_trim_catalog}; my $field_names = $attr->{ado_columns} ? $ado_schematables : $ado_dbi_schematables; my $rs; # # If the value of $catalog is '%' and $schema and $table name are empty # strings, the result set contains a list of catalog names. # if ( (defined $attr->{TABLE_CAT} && $attr->{TABLE_CAT} eq '%') && (defined $attr->{TABLE_SCHEM} && $attr->{TABLE_SCHEM} eq '' ) && (defined $attr->{TABLE_NAME} && $attr->{TABLE_NAME} eq '' ) ) { # Rule 19a # This is the easy way to determine catalog support. eval { local $Win32::OLE::Warn = 0; $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{adSchemaCatalogs} ); my $lastError = DBD::ADO::errors( $dbh ); $lastError = undef if $lastError =~ m/0x80020007/; die $lastError if $lastError; }; $dbh->trace_msg(" !! Eval of adSchemaCatalogs died: $@\n", 5 ) if $@; $dbh->trace_msg(" -- Rule 19a\n", 5 ); if ( $rs ) { $dbh->trace_msg(" -- Rule 19a, record set defined\n", 5 ); while ( !$rs->{EOF} ) { push @Rows, [ $rs->Fields(0)->{Value}, undef, undef, undef, undef ]; $rs->MoveNext; } } else { # The provider does not support the adSchemaCatalogs. Let's attempt # to still return a list of catalogs. $dbh->trace_msg(" -- Rule 19a, record set undefined\n", 5 ); my $sth = $dbh->table_info( { ado_trim_catalog => 1 } ); if ( $sth ) { my $ref = {}; my $Undef = 0; # for 'undef' hash keys (which mutate to '') while ( my $Row = $sth->fetch ) { defined $Row->[0] ? $ref->{$Row->[0]} = 1 : $Undef = 1; } push @Rows, [ undef, undef, undef, undef, undef ] if $Undef; push @Rows, [ $_, undef, undef, undef, undef ] for sort keys %$ref; } else { push @Rows, [ undef, undef, undef, undef, undef ]; } } } # # If the value of $schema is '%' and $catalog and $table are empty # strings, the result set contains a list of schema names. # elsif ( (defined $attr->{TABLE_CAT} && $attr->{TABLE_CAT} eq '' ) && (defined $attr->{TABLE_SCHEM} && $attr->{TABLE_SCHEM} eq '%') && (defined $attr->{TABLE_NAME} && $attr->{TABLE_NAME} eq '' ) ) { # Rule 19b eval { local $Win32::OLE::Warn = 0; $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{adSchemaSchemata} ); my $lastError = DBD::ADO::errors( $dbh ); $lastError = undef if $lastError =~ m/0x80020007/; die $lastError if $lastError; }; $dbh->trace_msg(" !! Eval of adSchemaSchemata died: $@\n", 5 ) if $@; $dbh->trace_msg(" -- Rule 19b\n", 5 ); if ( $rs ) { $dbh->trace_msg(" -- Rule 19b, record set defined\n", 5 ); while ( !$rs->{EOF} ) { push @Rows, [ $rs->Fields(0)->{Value}, $rs->Fields(1)->{Value}, undef, undef, undef ]; $rs->MoveNext; } } else { # The provider does not support the adSchemaSchemata. Let's attempt # to still return a list of schemas. $dbh->trace_msg(" -- Rule 19b, record set undefined\n", 5 ); my $sth = $dbh->table_info( { ado_trim_catalog => 1 } ); if ( $sth ) { my $ref = {}; my $Undef = 0; # for 'undef' hash keys (which mutate to '') while ( my $Row = $sth->fetch ) { defined $Row->[0] ? $ref->{$Row->[0]} = 1 : $Undef = 1; } push @Rows, [ undef, undef, undef, undef, undef ] if $Undef; push @Rows, [ undef, $_, undef, undef, undef ] for sort keys %$ref; } else { push @Rows, [ undef, undef, undef, undef, undef ]; } } } # # If the value of $type is '%' and $catalog, $schema, and $table are all # empty strings, the result set contains a list of table types. # elsif ( (defined $attr->{TABLE_CAT} && $attr->{TABLE_CAT} eq '' ) && (defined $attr->{TABLE_SCHEM} && $attr->{TABLE_SCHEM} eq '' ) && (defined $attr->{TABLE_NAME} && $attr->{TABLE_NAME} eq '' ) && (defined $attr->{TABLE_TYPE} && $attr->{TABLE_TYPE} eq '%') ) { # Rule 19c $dbh->trace_msg(" -- Rule 19c\n", 5 ); my @TableTypes = ('ALIAS','TABLE','SYNONYM','SYSTEM TABLE','VIEW','GLOBAL TEMPORARY','LOCAL TEMPORARY','SYSTEM VIEW'); # XXX for ( sort @TableTypes ) { push @Rows, [ undef, undef, undef, $_, undef ]; } } else { my @Criteria; for ( my $i = 0; $i < @$ado_dbi_schematables; $i++ ) { my $field = $ado_dbi_schematables->[$i]; $Criteria[$i] = $attr->{$field} if exists $attr->{$field}; } eval { local $Win32::OLE::Warn = 0; $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{adSchemaTables}, @Criteria ? \@Criteria : undef ); my $lastError = DBD::ADO::errors( $dbh ); $lastError = undef if $lastError =~ m/0x80020007/; die $lastError if $lastError; }; $dbh->trace_msg(" !! Eval of adSchemaTables died: $@\n", 5 ) if $@; if ( $rs ) { $rs->{Filter} = $attr->{ado_filter} if exists $attr->{ado_filter}; while ( !$rs->{EOF} ) { my @row = map { $rs->Fields( $_ )->{Value} } map { $sch_dbi_to_ado->{$_} } @$field_names; # Jan Dubois jand@activestate.com addition to handle changes # in Win32::OLE return of Variant types of data. for ( @row ) { $_ = $_->As( Win32::OLE::Variant::VT_BSTR() ) if defined $_ && UNIVERSAL::isa( $_,'Win32::OLE::Variant'); } if ( $attr->{ado_trim_catalog} ) { $row[0] =~ s/^(.*\\)// if defined $row[0]; # removes leading $row[0] =~ s/(\..*)$// if defined $row[0]; # removes file extension } push @Rows, \@row; $rs->MoveNext; } } else { push @Rows, [ undef, undef, undef, undef, undef ]; } } $rs->Close if $rs; $rs = undef; DBI->connect('dbi:Sponge:','','', { RaiseError => 1 } )->prepare( 'adSchemaTables', { rows => \@Rows , NAME => $field_names } ); } sub column_info { my ( $dbh, @Criteria ) = @_; my $QueryType = 'adSchemaColumns'; my @Rows; my $conn = $dbh->{ado_conn}; my $tmpCursorLocation = $conn->{CursorLocation}; $conn->{CursorLocation} = $Enums->{CursorLocationEnum}{adUseClient}; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, @Criteria ? \@Criteria : undef ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); $rs->{Sort} = 'TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION'; return if DBD::ADO::Failed( $dbh,"Can't set Sort"); while ( !$rs->{EOF} ) { my $AdoType = $rs->{DATA_TYPE }{Value}; my $ColFlags = $rs->{COLUMN_FLAGS}{Value}; my $IsLong = $ColFlags & $Enums->{FieldAttributeEnum}{adFldLong } ? 1 : 0; my $IsFixed = $ColFlags & $Enums->{FieldAttributeEnum}{adFldFixed} ? 1 : 0; my @SqlType = DBD::ADO::TypeInfo::ado2dbi( $AdoType, $IsFixed, $IsLong ); my $IsNullable = $rs->{IS_NULLABLE}{Value} ? 'YES' : 'NO'; my $ColSize = $rs->{NUMERIC_PRECISION }{Value} || $rs->{CHARACTER_MAXIMUM_LENGTH}{Value} || 0; # Default value to stop warnings ??? my $TypeName; my $ado_tis = DBD::ADO::TypeInfo::Find3( $dbh, $AdoType, $IsFixed, $IsLong ); $dbh->trace_msg(' ** ' . $rs->{COLUMN_NAME}{Value} . "($ColSize): $AdoType, $IsFixed, $IsLong\n", 6 ); # find the first type which has a large enough COLUMN_SIZE: for my $ti ( sort { $a->{COLUMN_SIZE} <=> $b->{COLUMN_SIZE} } @$ado_tis ) { $dbh->trace_msg(" ** => $ti->{TYPE_NAME}($ti->{COLUMN_SIZE})\n", 7 ); if ( $ti->{COLUMN_SIZE} >= $ColSize ) { $TypeName = $ti->{TYPE_NAME}; last; } } # unless $TypeName: Standard SQL type name??? my $Fields = [ $rs->{TABLE_CATALOG }{Value} # 0 TABLE_CAT , $rs->{TABLE_SCHEMA }{Value} # 1 TABLE_SCHEM , $rs->{TABLE_NAME }{Value} # 2 TABLE_NAME , $rs->{COLUMN_NAME }{Value} # 3 COLUMN_NAME , $SqlType[0] # 4 DATA_TYPE !!! , $TypeName # 5 TYPE_NAME !!! , $ColSize # 6 COLUMN_SIZE !!! MAX for *LONG* , $rs->{CHARACTER_OCTET_LENGTH}{Value} # 7 BUFFER_LENGTH !!! MAX for *LONG*, ... (e.g. num) , $rs->{NUMERIC_SCALE }{Value} # 8 DECIMAL_DIGITS ??? , undef # 9 NUM_PREC_RADIX !!! , $rs->{IS_NULLABLE }{Value} # 10 NULLABLE !!! , $rs->{DESCRIPTION }{Value} # 11 REMARKS , $rs->{COLUMN_DEFAULT }{Value} # 12 COLUMN_DEF , $SqlType[1] # 13 SQL_DATA_TYPE !!! , $SqlType[2] # 14 SQL_DATETIME_SUB !!! , $rs->{CHARACTER_OCTET_LENGTH}{Value} # 15 CHAR_OCTET_LENGTH !!! MAX for *LONG* , $rs->{ORDINAL_POSITION }{Value} # 16 ORDINAL_POSITION , $IsNullable # 17 IS_NULLABLE !!! ]; push @Rows, $Fields; $rs->MoveNext; } $rs->Close; undef $rs; $conn->{CursorLocation} = $tmpCursorLocation; DBI->connect('dbi:Sponge:','','', { RaiseError => 1 } )->prepare( $QueryType, { rows => \@Rows , NAME => [ qw( TABLE_CAT TABLE_SCHEM TABLE_NAME COLUMN_NAME DATA_TYPE TYPE_NAME COLUMN_SIZE BUFFER_LENGTH DECIMAL_DIGITS NUM_PREC_RADIX NULLABLE REMARKS COLUMN_DEF SQL_DATA_TYPE SQL_DATETIME_SUB CHAR_OCTET_LENGTH ORDINAL_POSITION IS_NULLABLE ) ] , TYPE => [ 12, 12, 12, 12, 5, 12, 4, 4, 5, 5, 5, 12, 12, 5, 5, 4, 4, 12 ] } ); } sub primary_key_info { my ( $dbh, @Criteria ) = @_; my $QueryType = 'adSchemaPrimaryKeys'; my @Rows; my $conn = $dbh->{ado_conn}; my $tmpCursorLocation = $conn->{CursorLocation}; $conn->{CursorLocation} = $Enums->{CursorLocationEnum}{adUseClient}; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, @Criteria ? \@Criteria : undef ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); $rs->{Sort} = 'TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, ORDINAL'; return if DBD::ADO::Failed( $dbh,"Can't set Sort"); while ( !$rs->{EOF} ) { my @Fields = (map { $_->{Value} } Win32::OLE::in( $rs->Fields ) ) [ 0,1,2,3,6,7 ]; push @Rows, \@Fields; $rs->MoveNext; } $rs->Close; undef $rs; $conn->{CursorLocation} = $tmpCursorLocation; DBI->connect('dbi:Sponge:','','', { RaiseError => 1 } )->prepare( $QueryType, { rows => \@Rows , NAME => [ qw( TABLE_CAT TABLE_SCHEM TABLE_NAME COLUMN_NAME KEY_SEQ PK_NAME ) ] , TYPE => [ 12, 12, 12, 12, 5, 12 ] } ); } sub foreign_key_info { my ( $dbh, @Criteria ) = @_; my $QueryType = 'adSchemaForeignKeys'; my $RefActions = { 'CASCADE' => 0 ,'RESTRICT' => 1 ,'SET NULL' => 2 ,'NO ACTION' => 3 ,'SET DEFAULT' => 4 }; my @Rows; my $conn = $dbh->{ado_conn}; my $tmpCursorLocation = $conn->{CursorLocation}; $conn->{CursorLocation} = $Enums->{CursorLocationEnum}{adUseClient}; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, @Criteria ? \@Criteria : undef ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); $rs->{Sort} = 'PK_TABLE_CATALOG, PK_TABLE_SCHEMA, PK_TABLE_NAME, FK_TABLE_CATALOG, FK_TABLE_SCHEMA, FK_TABLE_NAME'; return if DBD::ADO::Failed( $dbh,"Can't set Sort"); while ( !$rs->{EOF} ) { my @Fields = (map { $_->{Value} } Win32::OLE::in( $rs->Fields ) ) [ 0..3,6..9,12..14,16,15,17 ]; $Fields[ 9] = $RefActions->{$Fields[ 9]}; $Fields[10] = $RefActions->{$Fields[10]}; $Fields[13] += 4 if $Fields[13]; push @Rows, \@Fields; $rs->MoveNext; } $rs->Close; undef $rs; $conn->{CursorLocation} = $tmpCursorLocation; DBI->connect('dbi:Sponge:','','', { RaiseError => 1 } )->prepare( $QueryType, { rows => \@Rows , NAME => [ qw( PKTABLE_CAT PKTABLE_SCHEM PKTABLE_NAME PKCOLUMN_NAME FKTABLE_CAT FKTABLE_SCHEM FKTABLE_NAME FKCOLUMN_NAME KEY_SEQ UPDATE_RULE DELETE_RULE FK_NAME PK_NAME DEFERRABILITY ) ] , TYPE => [ 12, 12, 12, 12, 12, 12, 12, 12, 5, 5, 5, 12, 12, 5 ] } ); } sub statistics_info { my ( $dbh, $catalog, $schema, $table, $unique_only, $quick ) = @_; my $QueryType = 'adSchemaIndexes'; my $IndexType = { # 'table' 1 => 'btree' , 2 => 'hashed' , 3 => 'content' , 4 => 'other' # 'clustered' }; my $Collation = { 1 => 'A' , 2 => 'D' }; my @Rows; my $conn = $dbh->{ado_conn}; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, [ $catalog, $schema, undef, undef, $table ] ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); while ( !$rs->{EOF} ) { my @Fields = (map { $_->{Value} } Win32::OLE::in( $rs->Fields ) ) [ 0..2,7,4..5,9,16,17,20..23,8 ]; $Fields[ 3] = $Fields[ 3] ? 0 : 1; $Fields[ 6] = pop @Fields ? 'clustered' : defined $Fields[ 6] ? $IndexType->{$Fields[ 6]} : ''; $Fields[ 9] = $Collation->{$Fields[ 9]}; $rs->MoveNext; next if $unique_only && $Fields[ 3]; push @Rows, \@Fields; } $rs->Close; @Rows = sort { $a->[3] <=> $b->[3] || $a->[6] cmp $b->[6] || ( $a->[4] ||'') cmp ( $b->[4] ||'') || $a->[5] cmp $b->[5] || $a->[7] <=> $b->[7] } @Rows; { my $QueryType = 'adSchemaStatistics'; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, [ $catalog, $schema, $table ] ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); while ( !$rs->{EOF} ) { my @Fields = ( undef ) x 13; @Fields[ 6, 0..2, 10] = ('table', map { $_->{Value} } Win32::OLE::in( $rs->Fields ) ); unshift @Rows, \@Fields; $rs->MoveNext; } $rs->Close; } DBI->connect('dbi:Sponge:','','', { RaiseError => 1 } )->prepare( $QueryType, { rows => \@Rows , NAME => [ qw( TABLE_CAT TABLE_SCHEM TABLE_NAME NON_UNIQUE INDEX_QUALIFIER INDEX_NAME TYPE ORDINAL_POSITION COLUMN_NAME ASC_OR_DESC CARDINALITY PAGES FILTER_CONDITION ) ] , TYPE => [ 12, 12, 12, 5, 12, 12, 12, 5, 12, 1, 4, 4, 12 ] } ); } sub type_info_all { my ( $dbh ) = @_; return $dbh->{ado_ti_ver} == 2 ? &DBD::ADO::TypeInfo::type_info_all_2 : &DBD::ADO::TypeInfo::type_info_all_1; } sub ado_open_schema { my ( $dbh, $QueryType, @Criteria ) = @_; return $dbh->set_err( -910,"OpenSchema called with unknown parameter: $QueryType") unless exists $Enums->{SchemaEnum}{$QueryType}; my $conn = $dbh->{ado_conn}; my $rs = $conn->OpenSchema( $Enums->{SchemaEnum}{$QueryType}, @Criteria ? \@Criteria : undef ); return if DBD::ADO::Failed( $dbh,"Can't OpenSchema ($QueryType)"); return _rs_sth_prepare( $dbh, $rs ); } *OpenSchema = \&ado_open_schema; sub FETCH { my ( $dbh, $key ) = @_; if ( $key eq 'RowCacheSize') { return $dbh->{ado_cachesize}; } elsif ( $key =~ /^ado_/) { return $dbh->{ado_conn}{CommandTimeout} if $key eq 'ado_commandtimeout'; return $dbh->{$key} if exists $dbh->{$key}; my $value; eval { $key =~ s/^ado_//; local $Win32::OLE::Warn = 0; my $conn = $dbh->{ado_conn}; $value = $conn->{$key}; my $lastError = DBD::ADO::errors( $dbh ); $lastError = undef if $lastError =~ m/0x80020007/; die $lastError if $lastError; }; return $value unless $@; } return $dbh->SUPER::FETCH( $key ); } sub STORE { my ( $dbh, $key, $value ) = @_; if ( $key eq 'Warn') { $Win32::OLE::Warn = $value; return $dbh->SUPER::STORE( $key, $value ); } elsif ( $key eq 'RowCacheSize') { return $dbh->{ado_cachesize} = $value; } elsif ( $key eq 'AutoCommit') { if ( $dbh->{ado_txn_capable} ) { return $dbh->{AutoCommit} = _auto_commit( $dbh, $value ); } else { return $value if $value; Carp::croak("Can't disable AutoCommit: Provider does not support transactions."); } } elsif ( $key eq 'ado_commandtimeout') { $dbh->{ado_conn}{CommandTimeout} = $value; return if DBD::ADO::Failed( $dbh,"Can't set $key: $value"); return 1; } elsif ( $key =~ /^ado_/) { return $dbh->{$key} = $value; } elsif ( $key !~ /PrintError|RaiseError/) { eval { local $Win32::OLE::Warn = 0; my $conn = $dbh->{ado_conn}; $conn->{$key} = $value; my $lastError = DBD::ADO::errors( $dbh ); die $lastError if $lastError; }; Carp::carp $@ if $@ && $dbh->FETCH('Warn'); return $value unless $@; } return $dbh->SUPER::STORE( $key, $value ); } sub _auto_commit { my ( $dbh, $value ) = @_; my $cv = $dbh->FETCH('AutoCommit') || 0; if ( !$cv && $value ) { # Current off, turn on my $conn = $dbh->{ado_conn}; $conn->{Attributes} = 0; return if DBD::ADO::Failed( $dbh,"Can't set CommitRetaining"); $dbh->commit; return 1; } elsif ( $cv && !$value ) { my $conn = $dbh->{ado_conn}; $conn->{Attributes} = $Enums->{XactAttributeEnum}{adXactCommitRetaining} | $Enums->{XactAttributeEnum}{adXactAbortRetaining}; return if DBD::ADO::Failed( $dbh,"Can't set CommitRetaining"); $conn->BeginTrans; return if DBD::ADO::Failed( $dbh,"Can't Begin transaction"); return 0; } return $cv; # Didn't change the value. } sub do { my $dbh = shift; my $sql = shift; return $dbh->SUPER::do( $sql, @_ ) if @_; my $Rows = Win32::OLE::Variant->new( $DBD::ADO::Const::VT_I4_BYREF, 0 ); $dbh->{ado_conn}->Execute( $sql, $Rows, 129 ); # adCmdText | adExecuteNoRecords return if DBD::ADO::Failed( $dbh,"Can't Execute '$sql'"); return $Rows->Value || '0E0'; } sub DESTROY { my ( $dbh ) = @_; my $warn_handler = $SIG{__WARN__} || sub { warn @_ }; local $SIG{__WARN__} = sub { $warn_handler->(@_) unless $_[0] =~ /Not a Win32::OLE object/ }; $dbh->disconnect if $dbh->FETCH('Active'); return; } } # ====== DATABASE ====== { package DBD::ADO::st; # ====== STATEMENT ====== use strict; use Win32::OLE(); use Win32::OLE::Variant(); use DBD::ADO::TypeInfo(); use DBD::ADO::Const(); $DBD::ADO::st::imp_data_size = 0; my $Enums = DBD::ADO::Const->Enums; sub blob_read { my ( $sth, $n, $offset, $size, $attr ) = @_; my $Field = $sth->{ado_fields}[$n]; my $Chunk; if ( $Field->Attributes & $Enums->{FieldAttributeEnum}{adFldLong} ) { $Chunk = $Field->GetChunk( $size ); } else { $Chunk = substr $Field->Value, $offset, $size; } return defined $Chunk ? $Chunk : ''; } sub _params # Determine the number of parameters, if Refresh fails. { my $sql = shift; use Text::ParseWords; local $^W = 0; $sql =~ s/\n/ /; my $rtn = join(' ', grep { m/\?/ } grep { ! m/^['"].*\?/ } "ewords('\s+', 1, $sql ) ); my $cnt = ( $rtn =~ tr /?//) || 0; return $cnt; } sub _refresh { my ( $sth ) = @_; $sth->trace_msg(" -> _refresh\n", 3 ); my $conn = $sth->{ado_conn}; my $comm = $sth->{ado_comm}; my $Cnt = _params( $sth->FETCH('Statement') ); for ( 0 .. $Cnt - 1 ) { my $Parameter = $comm->CreateParameter("$_" , $Enums->{DataTypeEnum}{adVarChar} , $Enums->{ParameterDirectionEnum}{adParamInput} , 1 ,''); return if DBD::ADO::Failed( $sth,"Can't CreateParameter"); $comm->Parameters->Append( $Parameter ); return if DBD::ADO::Failed( $sth,"Can't Append Parameter"); } $sth->STORE('NUM_OF_PARAMS', $Cnt ); $sth->trace_msg(" <- _refresh\n", 3 ); return $Cnt; } sub bind_param { # my ( $sth, $n, $value, $attr ) = @_; # return _bind_param( $sth, $n, $value, $attr, FALSE, 0 ) return _bind_param( @_[0..3], 0, 0 ); } sub bind_param_inout { # my ( $sth, $n, $vref, $maxlen, $attr ) = @_; # return _bind_param( $sth, $n, $vref, $attr, TRUE, $maxlen ) return _bind_param( @_[0..2, 4], 1, $_[3] ); } sub _bind_param { my ( $sth, $n, $value, $attr, $is_bind_by_ref, $maxlen ) = @_; my $conn = $sth->{ado_conn}; my $comm = $sth->{ado_comm}; my $is_stored_procedure = $comm->{CommandType} == $Enums->{CommandTypeEnum}{adCmdStoredProc}; $attr = {} unless defined $attr; $attr = { TYPE => $attr } unless ref $attr; my $param_cnt = $sth->FETCH('NUM_OF_PARAMS') || _refresh( $sth ); --$param_cnt if $is_stored_procedure; return $sth->set_err( -915,"Bind Parameter $n outside current range of $param_cnt.") if $n > $param_cnt || $n < 1; if ( $is_bind_by_ref && defined $value ) { return $sth->set_err( -930,"Bind target for OUT parameter $n must be a scalar reference.") unless ref $value eq 'SCALAR'; if ( $sth->{TraceLevel} >= 5 ) { $sth->trace_msg(" -- discard old binding for $n", 5 ) if exists $sth->{ado_ParamRefs}{$n}; $sth->trace_msg(" -- bind param $n by reference to '$$value'; maxlen=$maxlen; attr={" . join(", ", map "$_ => $attr->{$_}", keys %$attr ) . "}\n", 5 ); } $sth->{ado_ParamRefs}{$n} = $value; $sth->{ado_ParamRefAttrs}{$n} = $attr; } else { # delete even if not ref; might need to clobber an old ref. $sth->trace_msg(" -- discard old binding for $n", 5 ) if exists $sth->{ado_ParamRefs}{$n}; delete $sth->{ado_ParamRefs}{$n}; delete $sth->{ado_ParamRefAttrs}{$n}; } # support adCmdStoredProc command format, where param 0 is @RETURN_VALUE my $i = $comm->Parameters->Item( $n - ( $is_stored_procedure ? 0 : 1 ) ); if ( exists $attr->{ado_type} ) { $i->{Type} = $attr->{ado_type}; } elsif ( exists $attr->{TYPE} ) { $i->{Type} = $DBD::ADO::TypeInfo::dbi2ado->{$attr->{TYPE}}; } if ( $is_bind_by_ref ) { $attr->{ado_maxlen} = $maxlen; } else { # factored-out to support delayed binding _assign_param( $sth, $n, $value, $attr, $i ); } return 1; } sub _assign_param { my ( $sth, $n, $value, $attr, $i ) = @_; $i = $sth->{ado_comm}->Parameters->Item( int( $i ) - ( $sth->{ado_comm}{CommandType} == $Enums->{CommandTypeEnum}{adCmdStoredProc} ? 0 : 1) ) unless defined $i; $attr = {} unless defined $attr; $sth->{ParamValues}{$n} = $value; if ( defined $value ) { if ( defined $attr->{ado_size} ) { $i->{Size} = $attr->{ado_size}; } elsif ( defined $attr->{ado_maxlen} && $attr->{ado_maxlen} > length $value ) { $i->{Size} = $attr->{ado_maxlen}; } else { $i->{Size} = length $value || 1; } if ( $i->{Type} == $Enums->{DataTypeEnum}{adVarBinary} || $i->{Type} == $Enums->{DataTypeEnum}{adLongVarBinary} ) { my $pic = Win32::OLE::Variant->new( Win32::OLE::Variant::VT_UI1() | Win32::OLE::Variant::VT_ARRAY(), $i->{Size} ); return $sth->set_err( -935, "Failed to create a Variant array of size $i->{Size}.") unless defined $pic; $pic->Put( $value ); $i->{Value} = $pic; $sth->trace_msg(" -- Binary: $i->{Type} $i->{Size}\n", 5 ); } else { $i->{Value} = $value; $sth->trace_msg(" -- Type : $i->{Type} $i->{Size}\n", 5 ); } } else { $i->{Value} = Win32::OLE::Variant->new( Win32::OLE::Variant::VT_NULL() ); } } sub _retrieve_out_params { my ( $sth ) = @_; my $comm = $sth->{ado_comm}; my $is_stored_procedure = $comm->{CommandType} == $Enums->{CommandTypeEnum}{adCmdStoredProc}; while ( my ( $n, $vref ) = each %{$sth->{ado_ParamRefs}} ) { my $value = $comm->Parameters->Item( $n - ( $is_stored_procedure ? 0 : 1 ) )->{Value}; # XXX perhaps should translate Variant null representation, here, first? $sth->{ParamValues}{$n} = $$vref = $value; $sth->trace_msg(" -- _retrieve_out_params : param => $n value => '$value'\n", 5 ); } if ($is_stored_procedure) { $sth->{ado_returnvalue} = $comm->Parameters->Item( 0 )->{Value}; $sth->trace_msg(" -- _retrieve_out_params : param => RETURN_VALUE value => '$sth->{ado_returnvalue}'\n", 5 ); } } sub execute { my ( $sth, @bind_values ) = @_; my $conn = $sth->{ado_conn}; my $comm = $sth->{ado_comm}; my $sql = $sth->FETCH('Statement'); my $rows = Win32::OLE::Variant->new( $DBD::ADO::Const::VT_I4_BYREF, 0 ); my $rs; $sth->finish if $sth->{Active}; $sth->bind_param( $_, $bind_values[$_-1] ) or return for 1 .. @bind_values; ## delayed binding of by-ref input[/output] parameters unless (@bind_values) { while ( my ( $n, $vref ) = each %{$sth->{ado_ParamRefs}} ) { my $i = $comm->Parameters->Item( $n - ($comm->{CommandType} == $Enums->{CommandTypeEnum}{adCmdStoredProc} ? 0 : 1) ); if ( $i->{Direction} & $Enums->{ParameterDirectionEnum}{adParamInput} ) { # probably don't need the ternary; creation of ado_maxlen should # guarantee that this will always exist my $attr = defined $sth->{ado_ParamRefAttrs}{$n} ? $sth->{ado_ParamRefAttrs}{$n} : undef; _assign_param( $sth, $n, $$vref, $attr, $i ); } } } # At this point a Command is ready to Execute. To allow for different # type of cursors, we need to create a Recordset object. # However, a Recordset Open does not return affected rows. So we need to # determine if a Recordset Open is needed, or a Command Execute. my $UseRecordSet = !defined $sth->{ado_usecmd} && ( defined $sth->{ado_cursortype} || defined $sth->{ado_users} ); my $UseResponseStream = $sth->{ado_executeoption} && ( $sth->{ado_executeoption} == $Enums->{ExecuteOptionEnum}{adExecuteStream} ); if ( $UseResponseStream ) { $sth->trace_msg(" -- Execute: Using Response Stream\n", 5 ); $comm->Execute( { 'Options' => $sth->{ado_executeoption} } ); return if DBD::ADO::Failed( $sth,"Can't Execute Command '$sql'"); _retrieve_out_params( $sth ); return $sth->{ado_responsestream}->ReadText(); } elsif ( $UseRecordSet ) { $rs = Win32::OLE->new('ADODB.RecordSet'); return if DBD::ADO::Failed( $sth,"Can't create 'ADODB.RecordSet'"); my $CursorType = $sth->{ado_cursortype} || 'adOpenForwardOnly'; $sth->trace_msg(" -- Open Recordset using CursorType '$CursorType'\n", 5 ); $rs->Open( $comm, undef, $Enums->{CursorTypeEnum}{$CursorType} ); return if DBD::ADO::Failed( $sth,"Can't Open Recordset for '$sql'"); _retrieve_out_params( $sth ); $sth->trace_msg(" -- CursorType: $rs->{CursorType}\n", 5 ); } else { $rs = $comm->Execute( $rows ); return if DBD::ADO::Failed( $sth,"Can't Execute Command '$sql'"); _retrieve_out_params( $sth ); } $rows = $rows->Value; # to make a DBD::Proxy client w/o Win32::OLE happy my @Fields; # some providers close the rs, e.g. after DROP TABLE if ( defined $rs && $rs->State ) { @Fields = Win32::OLE::in( $rs->Fields ); return if DBD::ADO::Failed( $sth,"Can't enumerate Fields"); } $sth->{ado_fields} = \@Fields; my $num_of_fields = @Fields; if ( $num_of_fields == 0 ) { # assume non-select statement $sth->trace_msg(" -- no fields (non-select statement?)\n", 5 ); # Clean up the record set that isn't used. if ( defined $rs && (ref $rs) =~ /Win32::OLE/) { $rs->Close if $rs && $rs->State & $Enums->{ObjectStateEnum}{adStateOpen}; } $rs = undef; $sth->{ado_rows} = $rows; return $rows || '0E0'; } if ( defined $sth->{ado_cachesize} && $sth->{ado_cachesize} > 0 ) { $sth->trace_msg(" -- changing CacheSize $rs->{CacheSize} => $sth->{ado_cachesize}\n", 5 ); $rs->{CacheSize} = $sth->{ado_cachesize}; my $lastError = DBD::ADO::errors( $sth ); $sth->set_err( 0, $lastError ) if $lastError; } my $Attributes; $Attributes |= $_->Attributes for @Fields; $sth->{ado_has_lob} = $Attributes & $Enums->{FieldAttributeEnum}{adFldLong} ? 1 : 0; $sth->{ado_rowset} = $rs; $sth->{ado_rownum} = 0; $sth->{ado_rows} = $rows; # $rs->RecordCount $sth->{ado_type} = [ map { $_->Type } @Fields ]; $sth->{NAME} = [ map { $_->Name } @Fields ]; $sth->{TYPE} = [ map { scalar DBD::ADO::TypeInfo::ado2dbi( $_->Type ) } @Fields ]; $sth->{PRECISION} = [ map { $_->Precision } @Fields ]; $sth->{SCALE} = [ map { $_->NumericScale } @Fields ]; $sth->{NULLABLE} = [ map { $_->Attributes & $Enums->{FieldAttributeEnum}{adFldMayBeNull} ? 1 : 0 } @Fields ]; $sth->STORE('Statement' , $rs->Source ); $sth->STORE('NUM_OF_FIELDS', $num_of_fields ); $sth->STORE('Active' , 1 ); # We need to return a true value for a successful select # -1 means total row count unavailable return $rows || '0E0'; # seems more reliable than $rs->RecordCount } sub more_results { my ( $sth ) = @_; my $rs = $sth->{ado_rowset}->NextRecordset; return if DBD::ADO::Failed( $sth,"Can't NextRecordset"); return undef unless $rs; delete $sth->{NUM_OF_FIELDS}; DBD::ADO::db::_rs_sth_prepare( $sth, $rs, $sth ); return 1; } sub rows { my ( $sth ) = @_; return unless defined $sth; my $rows = $sth->{ado_rows}; return defined $rows ? $rows : -1; } sub fetch { my ( $sth ) = @_; my $rs = $sth->{ado_rowset}; return $sth->set_err( -900,'Statement handle not marked as Active.') unless $sth->FETCH('Active'); return $sth->set_err( -905,'Recordset undefined, execute statement not called?') unless $rs; if ( $sth->{ado_rownum}++ > 0 ) { $rs->MoveNext; return if DBD::ADO::Failed( $sth,"Can't MoveNext"); } $sth->STORE('Active', 0 ), return if $rs->{EOF}; my @row; if ( $sth->{ado_has_lob} && $sth->FETCH('LongReadLen') < 2147483647 ) { my $LongReadLen = $sth->FETCH('LongReadLen'); my $LongTruncOk = $sth->FETCH('LongTruncOk'); for ( Win32::OLE::in( $rs->Fields ) ) { if ( $_->Attributes & $Enums->{FieldAttributeEnum}{adFldLong} ) { if ( $LongReadLen == 0 ) { push @row, undef; } else { my $ActualSize = $_->{ActualSize}; return if DBD::ADO::Failed( $sth,"Can't get ActualSize"); $sth->trace_msg(" -- ActualSize: $ActualSize, LongReadLen: $LongReadLen\n", 7 ); return $sth->set_err( -920,"LONG value truncated: $ActualSize > $LongReadLen") if !$LongTruncOk && $ActualSize > $LongReadLen; push @row, $_->GetChunk( $LongReadLen ); return if DBD::ADO::Failed( $sth,"Can't GetChunk"); } } else { push @row, $_->Value; } } } else { @row = map { $_->Value } Win32::OLE::in( $rs->Fields ); } # Jan Dubois jand@activestate.com addition to handle changes # in Win32::OLE return of Variant types of data. for ( @row ) { $_ = $_->As( Win32::OLE::Variant::VT_BSTR() ) if UNIVERSAL::isa( $_,'Win32::OLE::Variant'); } map { s/\s+$// } @row if $sth->FETCH('ChopBlanks'); $sth->{ado_rows} = $sth->{ado_rownum}; return $sth->_set_fbav( \@row ); } *fetchrow_arrayref = \&fetch; sub finish { my ( $sth ) = @_; my $rs = $sth->{ado_rowset}; $rs->Close if $rs && $rs->State; $sth->{ado_rowset} = undef; $sth->SUPER::finish; return 1; } sub FETCH { my ( $sth, $key ) = @_; return $sth->{ado_comm}{CommandTimeout} if $key eq 'ado_commandtimeout'; return $sth->{$key} if exists $sth->{$key}; return $sth->SUPER::FETCH( $key ); } sub STORE { my ( $sth, $key, $value ) = @_; if ( $key eq 'ado_commandtimeout') { $sth->{ado_comm}{CommandTimeout} = $value; return if DBD::ADO::Failed( $sth,"Can't set $key: $value"); return 1; } return $sth->{$key} = $value if exists $sth->{$key}; return $sth->SUPER::STORE( $key, $value ); } sub DESTROY { my ( $sth ) = @_; # not $sth->finish to avoid '!! ERROR: ... CLEARED by call to finish method' # e.g. in $dbh->do finish( $sth ); return; } } 1; =head1 NAME DBD::ADO - A DBI driver for Microsoft ADO (Active Data Objects) =head1 SYNOPSIS use DBI(); my $dbh = DBI->connect("dbi:ADO:$dsn", $usr, $pwd, $att ) or die $DBI::errstr; =head1 DESCRIPTION The DBD::ADO module supports ADO access on a Win32 machine. DBD::ADO is written to support the standard DBI interface to data sources. =head1 PREREQUISITES It is recommended that you use recent versions of the following prerequisites: =over =item DBI http://search.cpan.org/~timb/DBI/ =item Win32::OLE http://search.cpan.org/~jdb/Win32-OLE/ =item ADO http://msdn.microsoft.com/data/ =back =head1 DBI Methods =head2 connect Use the DBI connect method to establish a database connection: my $dbh = DBI->connect("dbi:ADO:$dsn", $usr, $pwd, $att ) or die $DBI::errstr; where $dsn - is an ADO ConnectionString $usr - is a user name $pwd - is a password $att - is a hash reference with additional attributes Typical connection attributes are RaiseError => 1 PrintError => 0 AutoCommit => 0 See the DBI module documentation for full details. Driver specific connection attributes are: ado_ConnectionTimeout ado_Mode These attributes initialize the corresponding ADO Connection properties before opening the connection. An ADO ConnectionString usually contains either a 'Provider' or a 'File Name' argument. If you omit these arguments, Provider defaults to MSDASQL (Microsoft OLE DB Provider for ODBC). Therefore you can pass an ODBC connection string (with DSN or DSN-less) as valid ADO connection string. If you use the OLE DB Provider for ODBC, it may be better to omit this additional layer and use DBD::ODBC with the ODBC driver. In addition the following attributes may be set in the connection string: Attributes CommandTimeout ConnectionString ConnectionTimeout CursorLocation DefaultDatabase IsolationLevel Mode B This feature is supported for backward compatibility. It's saver to use the driver specific attributes described above. Anyway, the application is responsible for passing the correct values when setting any of these attributes. See the ADO documentation for more information on connection strings. ADO ConnectionString examples: test File Name=test.udl Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\data\test.mdb Provider=VFPOLEDB;Data Source=C:\data\test.dbc Provider=MSDAORA For more examples, see e.g.: http://www.able-consulting.com/tech.htm =head2 data_sources Because ADO doesn't provide a data source repository, DBD::ADO uses it's own. It tries to load Local::DBD::ADO::DSN and expects an array of hashes describing the data sources. See ex/Local/DBD/ADO/DSN.pm for an example. B This is experimental and may change. B Check for the unlikly case that a file Local/DBD/ADO/DSN.pm exists in your module search path which causes unwanted side effects when loaded. =head2 prepare The B methods allows attributes (see DBI): $sth = $dbh->prepare( $statement ) or die $dbh->errstr; $sth = $dbh->prepare( $statement, \%attr ) or die $dbh->errstr; DBD::ADO's prepare() supports setting the CursorType, e.g.: $sth = $dbh->prepare( $sql, { ado_cursortype => 'adOpenForwardOnly' } ) ... # the CursorType attribute is deprecated: $sth = $dbh->prepare( $sql, { CursorType => 'adOpenForwardOnly' } ) ... Possible cursortypes are: adOpenForwardOnly (default) adOpenKeyset adOpenDynamic adOpenStatic It may be necessary to prepare the statement using cursortype 'adOpenStatic' when using a statement handle within a statement handle: while( my $table = $sth1->fetchrow_hashref ) { ... my $col = $sth2->fetchrow_hashref; ... } Changing the CursorType is a solution to the following problem: Can't execute statement 'select * from authors': Lasterror : -2147467259 OLE exception from "Microsoft OLE DB Provider for SQL Server": Cannot create new connection because in manual or distributed transaction mode. Win32::OLE(0.1403) error 0x80004005: "Unspecified error" in METHOD/PROPERTYGET "Open" Description : Cannot create new connection because in manual or distributed transaction mode. HelpContext : 0 HelpFile : NativeError : 0 Number : -2147467259 Source : Microsoft OLE DB Provider for SQL Server SQLState : =head2 bind_param Normally, the datatypes of placeholders are known after the statement is prepared. In this case, you don't need to provide any type information: $sth->bind_param( 1, $value ); Sometimes, you need to specify a type for the parameter, e.g.: $sth->bind_param( 1, $value, SQL_NUMERIC ); As a last resort, you can provide an ADO-specific type, e.g.: $sth->bind_param( 1, $value, { ado_type => 6 } ); # adCurrency If no type is given (neither by the provider nor by you), the datatype defaults to SQL_VARCHAR (adVarChar). =head2 bind_param_inout This can be utilized (with IN parameters) to support simple call-by-reference, allowing for lazy parameter binding. $sth->bind_param_inout( 1, \$value, 1024 ); The contents of $value will not be dereferenced until the call to C<$sth-Eexecute();> is made. To use IN/OUT parameter types with stored procedures, remember that you will need to specify the appropriate command type when preparing the statement, e.g.: $sth = $dbh->prepare('sproc_name', { CommandType => 'adCmdStoredProc' } ); After execution, all call-by-reference parameters will be updated with the parameter values reported by the ADO command object. For stored procedures, the parameter at index C<0> is treated as the return value of the procedure. After execution, it is copied to the C<$sth-E{ado_returnvalue}> attribute. =head2 Type info There exists two implementations of type_info_all(). Which version is used depends on the ado_ti_ver database handle attribute: =over =item C<$dbh-E{ado_ti_ver} = 1> The first implementations tries to find for various DBI types a set of ADO types supported by the provider. The algorithm is highly sophisticated. It tends to generate more duplicate type codes and names. =item C<$dbh-E{ado_ti_ver} = 2> (default) The second implementations is quite straightforward. It uses the set which the provider returns and tries to map various ADO codes to DBI/ODBC codes. The mapping is similar to the one used in column_info(). Duplicate type codes and names tend to occur less often. The rows are ordered by DATA_TYPE, but not necessarily by 'how closely each type maps to the corresponding ODBC SQL data type'. This second sort criterion is difficult to achieve. =back =head2 table_info $sth = $dbh->table_info( $catalog, $schema, $table, $type, \%attr ); $sth = $dbh->table_info( \%attr ); # deprecated $sth = $dbh->table_info( undef, undef, undef,'VIEW', { ado_columns => 1, ado_trim_catalog => 0, ado_filter => q{TABLE_NAME LIKE 'C%'}, } ); Returns an active statement handle that can be used to fetch information about tables and views that exist in the database. By default the handle contains the columns described in the DBI documentation: TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TABLE_TYPE, REMARKS DBD::ADO allows selection criteria to be specified in the attributes hash for backward compatibility. In this way the record set can be restricted, for example, to only include tables of type 'VIEW': %attr = ( TABLE_TYPE => 'VIEW') # deprecated You can add criteria for any of the following columns: TABLE_CAT, TABLE_SCHEM, TABLE_NAME, TABLE_TYPE =over =item B (deprecated: Filter) DBD::ADO also allows the recordset to be filtered on a criteria string: a string made up of one or more individual clauses concatenated with AND or OR operators. %attr = ( ado_filter => q{TABLE_TYPE LIKE 'SYSTEM%'} ) The criteria string is made up of clauses in the form FieldName-Operator-Value. This is more flexible than using column criteria in that the filter allows a number of operators: <, >, <=, >=, <>, =, or LIKE The Fieldname must be one of the ADO 'TABLES Rowset' column names: TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, DESCRIPTION, TABLE_GUID, TABLE_PROPID, DATE_CREATED, DATE_MODIFIED Value is the value with which you will compare the field values (for example,'Smith', #8/24/95#, 12.345, or $50.00). Use single quotes with strings and pound signs (#) with dates. For numbers, you can use decimal points, dollar signs, and scientific notation. If Operator is LIKE, Value can use wildcards. Only the asterisk (*) and percent sign (%) wild cards are allowed, and they must be the last character in the string. Value cannot be null. =item B (deprecated: ADO_Columns) Additional ADO-only fields will be included if the C attribute is set to true: %attr = ( ado_columns => 1 ); =item B (deprecated: Trim_Catalog) Some ADO providers include path info in the TABLE_CAT column. This information will be trimmed if the C attribute is set to true: %attr = ( ado_trim_catalog => 1 ); =back =head2 tables @names = $dbh->tables( $catalog, $schema, $table, $type, \%attr ); Returns a list of table and view names. Accepts any of the attributes described in the L method: @names = $dbh->tables( undef, undef, undef,'VIEW'); =head2 statistics_info The row for TYPE 'table' is always the first row. Its PAGES field will be NULL (C). The C<$quick> argument is ignored. =head1 ADO-specific methods =head2 ado_open_schema $sth = $dbh->ado_open_schema( $QueryType, @Criteria ) or die ...; This method can be used to obtain database schema information from the provider. It returns a valid statement handle upon success. C<$QueryType> may be any valid ADO SchemaEnum name such as adSchemaTables adSchemaIndexes adSchemaProviderTypes C<@Criteria> (optional) is a list of query constraints depending on each C<$QueryType>. Example: my $sth = $dbh->ado_open_schema('adSchemaCheckConstraints','Catalog1'); B With DBI version 1.36 and earlier, the func() method has to be used to call private methods implemented by the driver: $h->func( @func_arguments, $func_name ) or die ...; where C<$func_name> is 'ado_open_schema'. You can use 'OpenSchema' for backward compatibility. Example: my $sth = $dbh->func('adSchemaCheckConstraints','Catalog1','OpenSchema'); See ex/OpenSchema.pl for a working example. =head1 Error handling An ADO provider may return a collection of more than one error. After stringification , DBD::ADO concatenates these error messages to set the errstr value of the handle. However, the err value is set to the LastError known to Win32::OLE. Usually, this is the native OLE DB error code. These codes contain the following severity codes (see oledberr.h from the MDAC SDK): 00 - Success 01 - Informational 10 - Warning 11 - Error The err value is set to 0 if all error codes belong to the Success or Informational category, which doesn't trigger the normal DBI error handling mechanisms. The standard SQLSTATE is seldom supported by ADO providers and cannot be relied on. The db/st handle attribute 'ado_max_errors' limits the number of errors extracted from the errors collection. To avoid time-consuming processing of huge error collections, it defaults to 50. =head1 ADO specific attributes =head2 ado_commandtimeout (all handles, decimal, inherited) This attribute indicates the time (in seconds) after which a command is canceled. Typically, cancellation may occur during a long running C method. Not all ADO providers support this functionality. Whereas ADO's Command object doesn't inherit the Connection's CommandTimeout setting, DBD::ADO's statement handle is initialized with the ado_commandtimeout attribute of its associated database handle. =head1 LOB support Since version 2.92, DBD::ADO supports the DBI handle attributes C and C. C defaults to C<2147483647> (for backwards compatibility). For multibyte strings, it's unspecified whether C means I or I. It's passed through to ADO's C method and C property. B Unexpected truncation errors may occur if the ADO provider (e.g. Microsoft.Jet.OLEDB.4.0) uses I for C but I for C. B The ADO provider may have problems if the long field isn't the last one in the list of selected columns, e.g.: Description : Catastrophic failure HelpContext : 5000000 HelpFile : NativeError : Number : -2147418113 Source : Microsoft JET Database Engine SQLState : The (experimental) C method ignores the C<$offset> argument for long fields (ADO maintains a I internally). To ensure that C reads from the beginning, C must be set to C<0>. =head1 CAVEATS =head2 Character set Proper Unicode support depends on all components involved in your application: the DBMS, the ADO provider, Perl and some perl modules. In short: Perl 5.8 and Win32::OLE 0.16 (or later) are strongly recommended and Win32::OLE has to be prepared to use the correct codepage: Win32::OLE->Option( CP => Win32::OLE::CP_UTF8 ); More detailed notes can be found at http://purl.net/stefan_ram/pub/perl_unicode_en =head2 ADO providers =over =item SQLOLEDB may truncate inserted strings It seems that the size of the first inserted string is sticky. Inserted strings longer than the first one are truncated. As a workaround, the C attribute for C was introduced in version 2.95: $sth->bind_param( $p_num, $bind_value, { ado_size => $size } ); =item MSDAORA may have problems with client-side cursors MSDAORA may throw an error, return an empty result set or loop forever when C is set to C. This setting is used in catalog methods for sorting and filtering. =back =head1 AUTHORS Tim Bunce and Phlip. With many thanks to Jan Dubois and Jochen Wiedmann for additions, debuggery and general help. Special thanks to Thomas Lowery, who maintained this module 2001-2003. Current maintainer is Steffen Goeldner. =head1 SUPPORT This software is supported via the dbi-users mailing list. For more information and to keep informed about progress you can join the mailing list by sending a message to dbi-users-help@perl.org Please post details of any problems (or changes you needed to make) to dbi-users@perl.org and CC them to me (sgoeldner@cpan.org). =head1 COPYRIGHT Copyright (c) 1998, Tim Bunce Copyright (c) 1999, Tim Bunce, Phlip, Thomas Lowery Copyright (c) 2000, Tim Bunce, Thomas Lowery Copyright (c) 2001, Tim Bunce, Thomas Lowery, Steffen Goeldner Copyright (c) 2002, Thomas Lowery, Steffen Goeldner Copyright (c) 2003, Thomas Lowery, Steffen Goeldner Copyright (c) 2004-2011 Steffen Goeldner All rights reserved. You may distribute under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. =head1 SEE ALSO =head2 Books ADO Reference book: ADO 2.0 Programmer's Reference David Sussman and Alex Homer Wrox ISBN 1-861001-83-5 ADO: ActiveX Data Objects Jason T. Roff O'Reilly ISBN 1-56592-415-0 http://www.oreilly.com/catalog/ado/index.html If there's anything better please let me know. =head2 Perl modules L, L, L =cut