line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# DBD::MVS_FTPSQL - DBD driver to query IBM DB2 mainframe databases through an FTP server.
|
2
|
|
|
|
|
|
|
#
|
3
|
|
|
|
|
|
|
# Copyright (c) 2007 Clemente Biondo
|
4
|
|
|
|
|
|
|
#
|
5
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public
|
6
|
|
|
|
|
|
|
# License or the Artistic License, as specified in the Perl README file.
|
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
119610
|
use warnings;
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
49
|
|
9
|
1
|
|
|
1
|
|
6
|
use strict;
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
42
|
|
10
|
|
|
|
|
|
|
#require 5.004;
|
11
|
|
|
|
|
|
|
require DBI;
|
12
|
1
|
|
|
1
|
|
4
|
use Net::FTP;
|
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
44
|
|
13
|
1
|
|
|
1
|
|
5
|
use IO::File;
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
318
|
|
14
|
1
|
|
|
1
|
|
7
|
use Carp qw(croak);
|
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
3888
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
package DBD::MVS_FTPSQL;
|
17
|
|
|
|
|
|
|
our $VERSION = '0.38.14';
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
our $drh = undef; # Driver handle. Every thread has one (see CLONE method)
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# Driver handle constructor
|
22
|
|
|
|
|
|
|
sub driver {
|
23
|
1
|
50
|
|
1
|
0
|
2156
|
return $drh if $drh; # If already created, return it
|
24
|
1
|
|
|
|
|
3
|
my ($class, $attr) = @_;
|
25
|
1
|
|
|
|
|
2
|
$class .= "::dr";
|
26
|
|
|
|
|
|
|
|
27
|
1
|
|
|
|
|
6
|
return DBI::_new_drh($class, {
|
28
|
|
|
|
|
|
|
'Name' => 'MVS_FTPSQL',
|
29
|
|
|
|
|
|
|
'Version' => $VERSION,
|
30
|
|
|
|
|
|
|
'Attribution' => 'DBD::MVS_FTPSQL by Clemente Biondo '.
|
31
|
|
|
|
|
|
|
''
|
32
|
|
|
|
|
|
|
});
|
33
|
|
|
|
|
|
|
}
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
#Ensure that two different ithreads don't' share the same driver object
|
36
|
0
|
|
|
0
|
|
0
|
sub CLONE {undef $drh;}
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
#End of DBD::MVS_FTPSQL
|
39
|
|
|
|
|
|
|
package DBD::MVS_FTPSQL::dr;
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
$DBD::MVS_FTPSQL::dr::imp_data_size = 0;
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# Database handle constructor.
|
44
|
|
|
|
|
|
|
# Some database specific verifications, default settings and the like can
|
45
|
|
|
|
|
|
|
# go here.
|
46
|
|
|
|
|
|
|
sub connect {
|
47
|
0
|
|
|
0
|
|
0
|
my ($drh, $dr_dsn, $username, $password, $attr) = @_;
|
48
|
0
|
|
|
|
|
0
|
my $driver_prefix = "mvs_ftpsql_";
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
#The dr_dsn string is in "ODBC" format name1=value1;...;nameN=valueN
|
51
|
0
|
|
|
|
|
0
|
foreach my $var ( split /;/, $dr_dsn ) {
|
52
|
0
|
|
|
|
|
0
|
my ($attr_name, $attr_value) = split '=', $var, 2;
|
53
|
0
|
0
|
|
|
|
0
|
return $drh->set_err(1, "Can't parse DSN part '$var'")
|
54
|
|
|
|
|
|
|
unless defined $attr_value;
|
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# add driver prefix to attribute name if it doesn't have it already
|
57
|
0
|
0
|
|
|
|
0
|
$attr_name = $driver_prefix.$attr_name
|
58
|
|
|
|
|
|
|
unless $attr_name =~ /^$driver_prefix/o;
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# Store attribute into %$attr, replacing any existing value.
|
61
|
|
|
|
|
|
|
# The DBI will STORE() these into $dbh after we've connected
|
62
|
0
|
|
|
|
|
0
|
$attr->{$attr_name} = $attr_value;
|
63
|
|
|
|
|
|
|
}
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
|
66
|
0
|
0
|
|
|
|
0
|
return $drh->set_err(1, "Error in the dns string: you must specify the ".
|
67
|
|
|
|
|
|
|
"mainframe hostname.")
|
68
|
|
|
|
|
|
|
unless defined ($attr->{mvs_ftpsql_hostname});
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Get the attributes we'll use to connect.
|
71
|
|
|
|
|
|
|
# We use delete here because these no need to STORE them
|
72
|
0
|
|
|
|
|
0
|
my $host = delete $attr->{mvs_ftpsql_hostname};
|
73
|
0
|
|
0
|
|
|
0
|
my $port = delete $attr->{mvs_ftpsql_port} || 21;
|
74
|
0
|
|
0
|
|
|
0
|
my $timeout = delete $attr->{mvs_ftpsql_timeout} || 120;
|
75
|
0
|
|
0
|
|
|
0
|
my $remote_directory = delete $attr->{mvs_ftpsql_remote_directory} || '';
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
#Additional default attributes
|
78
|
0
|
0
|
|
|
|
0
|
$attr->{mvs_ftpsql_remote_prefix} = 'FSQL'
|
79
|
|
|
|
|
|
|
unless $attr->{mvs_ftpsql_remote_prefix};
|
80
|
|
|
|
|
|
|
|
81
|
0
|
0
|
|
|
|
0
|
$attr->{mvs_ftpsql_ssid} = ''
|
82
|
|
|
|
|
|
|
unless $attr->{mvs_ftpsql_ssid};
|
83
|
0
|
|
|
|
|
0
|
my $debug = 0;
|
84
|
0
|
0
|
|
|
|
0
|
my $conn = Net::FTP->new( $host
|
85
|
|
|
|
|
|
|
,Port => $port
|
86
|
|
|
|
|
|
|
,Debug => $debug
|
87
|
|
|
|
|
|
|
,Timeout => $timeout
|
88
|
|
|
|
|
|
|
,Passive => 1 )
|
89
|
|
|
|
|
|
|
or return $drh->set_err(1,"Cannot establish an ftp connection to host ".
|
90
|
|
|
|
|
|
|
"$host at port $port. Error received: $!");
|
91
|
|
|
|
|
|
|
|
92
|
0
|
0
|
|
|
|
0
|
return $drh->set_err(1,"Login failed. Error received: ". $conn->message)
|
93
|
|
|
|
|
|
|
unless ($conn->login($username,$password));
|
94
|
|
|
|
|
|
|
|
95
|
0
|
0
|
|
|
|
0
|
unless ($remote_directory eq '') {
|
96
|
0
|
|
|
|
|
0
|
$remote_directory =~ s/^([^\/])/\/\/$1/;
|
97
|
0
|
0
|
|
|
|
0
|
return $drh->set_err(1,"Remote directory not accepted. Error received: ".
|
98
|
|
|
|
|
|
|
$conn->message) unless ($conn->cwd($remote_directory));
|
99
|
|
|
|
|
|
|
}
|
100
|
|
|
|
|
|
|
|
101
|
0
|
|
|
|
|
0
|
my ($outer, $dbh) = DBI::_new_dbh($drh, { Name => $dr_dsn });
|
102
|
|
|
|
|
|
|
#$dbh->STORE('Active', 1 );
|
103
|
|
|
|
|
|
|
|
104
|
0
|
|
|
|
|
0
|
$dbh->{mvs_ftpsql_connection} = $conn;
|
105
|
0
|
|
|
|
|
0
|
return $outer;
|
106
|
|
|
|
|
|
|
}
|
107
|
|
|
|
|
|
|
|
108
|
0
|
|
|
0
|
|
0
|
sub data_sources {return undef;}
|
109
|
|
|
|
|
|
|
|
110
|
1
|
|
|
1
|
|
1758
|
sub disconnect_all {}
|
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
#End of DBD::MVS_FTPSQL::dr
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
package DBD::MVS_FTPSQL::db;
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
$DBD::MVS_FTPSQL::db::imp_data_size = 0;
|
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
#Todo:
|
119
|
|
|
|
|
|
|
# primary_key
|
120
|
|
|
|
|
|
|
# foreign_key_info
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# The get_info function was automatically generated by
|
123
|
|
|
|
|
|
|
# DBI::DBD::Metadata::write_getinfo_pm v1.05.
|
124
|
|
|
|
|
|
|
sub get_info {
|
125
|
0
|
|
|
0
|
|
|
my($dbh, $info_type) = @_;
|
126
|
0
|
|
|
|
|
|
require DBD::MVS_FTPSQL::GetInfo;
|
127
|
0
|
|
|
|
|
|
my $v = $DBD::MVS_FTPSQL::GetInfo::info{int($info_type)};
|
128
|
0
|
0
|
|
|
|
|
$v = $v->($dbh) if ref $v eq 'CODE';
|
129
|
0
|
|
|
|
|
|
return $v;
|
130
|
|
|
|
|
|
|
}
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# The type_info_all function was automatically generated by
|
133
|
|
|
|
|
|
|
# DBI::DBD::Metadata::write_typeinfo_pm v1.05.
|
134
|
|
|
|
|
|
|
sub type_info_all {
|
135
|
0
|
|
|
0
|
|
|
my ($dbh) = @_;
|
136
|
0
|
|
|
|
|
|
require DBD::MVS_FTPSQL::TypeInfo;
|
137
|
0
|
|
|
|
|
|
return [ @$DBD::MVS_FTPSQL::TypeInfo::type_info_all ];
|
138
|
|
|
|
|
|
|
}
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
#Note: blanks must become undef
|
141
|
|
|
|
|
|
|
sub column_info {
|
142
|
0
|
|
|
0
|
|
|
my $dbh = shift;
|
143
|
0
|
|
|
|
|
|
my $catalog = shift; #not applicable so not used at all
|
144
|
0
|
|
|
|
|
|
my $schema = shift;
|
145
|
0
|
|
|
|
|
|
my $table = shift;
|
146
|
0
|
|
|
|
|
|
my $column = shift;
|
147
|
0
|
|
|
|
|
|
my @where = ();
|
148
|
|
|
|
|
|
|
|
149
|
0
|
|
|
|
|
|
foreach ( [\$schema,'TBCREATOR'], [\$table,'TBNAME'], [\$column,'NAME']) {
|
150
|
0
|
0
|
0
|
|
|
|
if (defined(${$_->[0]}) && ${$_->[0]} ne '') {
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
151
|
0
|
0
|
|
|
|
|
my $op = index(${$_->[0]},'%') < 0 ? '=' : 'LIKE';
|
|
0
|
|
|
|
|
|
|
152
|
0
|
|
|
|
|
|
push(@where,$_->[1]." $op '".${$_->[0]}."'");
|
|
0
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
}
|
154
|
|
|
|
|
|
|
}
|
155
|
|
|
|
|
|
|
|
156
|
0
|
0
|
|
|
|
|
my $where = (($#where >= 0) ? 'WHERE ' : '') . join (' AND ',@where);
|
157
|
0
|
|
0
|
|
|
|
my $sth = $dbh->prepare(<
|
158
|
|
|
|
|
|
|
select
|
159
|
|
|
|
|
|
|
'' as TABLE_CAT
|
160
|
|
|
|
|
|
|
,TBCREATOR as TABLE_SCHEM
|
161
|
|
|
|
|
|
|
,TBNAME as TABLE_NAME
|
162
|
|
|
|
|
|
|
,NAME as COLUMN_NAME
|
163
|
|
|
|
|
|
|
,'' as DATA_TYPE
|
164
|
|
|
|
|
|
|
,COLTYPE as TYPE_NAME
|
165
|
|
|
|
|
|
|
,LENGTH as COLUMN_SIZE
|
166
|
|
|
|
|
|
|
,'' as BUFFER_LENGTH
|
167
|
|
|
|
|
|
|
,LENGTH - SCALE as DECIMAL_DIGITS
|
168
|
|
|
|
|
|
|
,'' as NUM_PREC_RADIX
|
169
|
|
|
|
|
|
|
,case NULLS when 'N' then
|
170
|
|
|
|
|
|
|
'0' else '1' end as NULLABLE
|
171
|
|
|
|
|
|
|
,REMARKS as REMARKS
|
172
|
|
|
|
|
|
|
,DEFAULTVALUE as COLUMN_DEF
|
173
|
|
|
|
|
|
|
,'' as SQL_DATA_TYPE
|
174
|
|
|
|
|
|
|
,'' as SQL_DATETIME_SUB
|
175
|
|
|
|
|
|
|
,'' as CHAR_OCTET_LENGTH
|
176
|
|
|
|
|
|
|
,COLNO as ORDINAL_POSITION
|
177
|
|
|
|
|
|
|
,case NULLS when 'N' then
|
178
|
|
|
|
|
|
|
'NO' else 'YES' end as IS_NULLABLE
|
179
|
|
|
|
|
|
|
from sysibm.syscolumns
|
180
|
|
|
|
|
|
|
$where
|
181
|
|
|
|
|
|
|
order by TBCREATOR,TBNAME,NAME,COLNO
|
182
|
|
|
|
|
|
|
with ur
|
183
|
|
|
|
|
|
|
EOSQL
|
184
|
0
|
0
|
|
|
|
|
$sth->execute() || Carp::croak ("Execute operation failed:$!");
|
185
|
0
|
|
|
|
|
|
return $sth;
|
186
|
|
|
|
|
|
|
}
|
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
#Note: blanks must become undef
|
189
|
|
|
|
|
|
|
sub table_info {
|
190
|
0
|
|
|
0
|
|
|
my $dbh = shift;
|
191
|
0
|
|
|
|
|
|
my $catalog = shift; #not applicable so not used at all
|
192
|
0
|
|
|
|
|
|
my $schema = shift;
|
193
|
0
|
|
|
|
|
|
my $table = shift;
|
194
|
0
|
|
|
|
|
|
my $type = shift;
|
195
|
|
|
|
|
|
|
|
196
|
0
|
|
|
|
|
|
my %type2flag = (
|
197
|
|
|
|
|
|
|
'ALIAS' => 'A'
|
198
|
|
|
|
|
|
|
,'GLOBAL TEMPORARY' => 'G'
|
199
|
|
|
|
|
|
|
,'SYSTEM TABLE' => 'T'
|
200
|
|
|
|
|
|
|
,'TABLE' => 'T'
|
201
|
|
|
|
|
|
|
,'VIEW' => 'V'
|
202
|
|
|
|
|
|
|
,'AUXILIARY TABLE' => 'X'
|
203
|
|
|
|
|
|
|
,'MATERIALIZED QUERY TABLE' => 'M'
|
204
|
|
|
|
|
|
|
);
|
205
|
|
|
|
|
|
|
|
206
|
0
|
|
|
|
|
|
my $flag_table = $type2flag{$type};
|
207
|
0
|
0
|
|
|
|
|
$flag_table = '' unless(defined($type2flag{$type}));
|
208
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
|
my @where = ();
|
210
|
|
|
|
|
|
|
|
211
|
0
|
|
|
|
|
|
foreach ( [\$schema,'CREATOR'], [\$table,'NAME'], [\$flag_table,'TYPE']) {
|
212
|
0
|
0
|
0
|
|
|
|
if (defined(${$_->[0]}) && ${$_->[0]} ne '') {
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
213
|
0
|
0
|
|
|
|
|
my $op = index(${$_->[0]},'%') < 0 ? '=' : 'LIKE';
|
|
0
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
|
push(@where,$_->[1]." $op '".${$_->[0]}."'");
|
|
0
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
}
|
216
|
|
|
|
|
|
|
}
|
217
|
|
|
|
|
|
|
|
218
|
0
|
0
|
|
|
|
|
my $where = (($#where >= 0) ? 'WHERE ' : '') . join (' AND ',@where);
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
#There is no need of escaping because only the first sql instruction can be
|
221
|
|
|
|
|
|
|
#executed and this driver alllows only selects.
|
222
|
|
|
|
|
|
|
# create a "blank" statement handle
|
223
|
0
|
|
0
|
|
|
|
my $sth = $dbh->prepare(<
|
224
|
|
|
|
|
|
|
SELECT
|
225
|
|
|
|
|
|
|
'' AS TABLE_CAT
|
226
|
|
|
|
|
|
|
,NAME as TABLE_NAME
|
227
|
|
|
|
|
|
|
,CREATOR as TABLE_SCHEM
|
228
|
|
|
|
|
|
|
,case when type = 'A' then 'ALIAS'
|
229
|
|
|
|
|
|
|
when type = 'G' then 'GLOBAL TEMPORARY'
|
230
|
|
|
|
|
|
|
when type = 'T' and name like 'SYS' then 'SYSTEM TABLE'
|
231
|
|
|
|
|
|
|
when type = 'T' and name not like 'SYS' then 'TABLE'
|
232
|
|
|
|
|
|
|
when type = 'V' then 'VIEW'
|
233
|
|
|
|
|
|
|
when type = 'X' then 'AUXILIARY TABLE'
|
234
|
|
|
|
|
|
|
when type = 'M' then 'MATERIALIZED QUERY TABLE'
|
235
|
|
|
|
|
|
|
else 'UNKNOWN' END AS TABLE_TYPE
|
236
|
|
|
|
|
|
|
,REMARKS
|
237
|
|
|
|
|
|
|
FROM SYSIBM.SYSTABLES
|
238
|
|
|
|
|
|
|
$where
|
239
|
|
|
|
|
|
|
WITH UR
|
240
|
|
|
|
|
|
|
EOSQL
|
241
|
0
|
0
|
|
|
|
|
$sth->execute() || Carp::croak ("Execute operation failed:$!");
|
242
|
0
|
|
|
|
|
|
return $sth;
|
243
|
|
|
|
|
|
|
}
|
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub ping {
|
246
|
0
|
|
|
0
|
|
|
my $dbh = shift;
|
247
|
0
|
0
|
|
|
|
|
if ($dbh->FETCH('Active')) {
|
248
|
0
|
|
|
|
|
|
my $warnmsg = "";
|
249
|
|
|
|
|
|
|
{
|
250
|
0
|
|
|
0
|
|
|
local $SIG{__WARN__} = sub {$warnmsg=shift;};
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
251
|
0
|
|
|
|
|
|
$dbh->{mvs_ftpsql_connection}->quot('noop');
|
252
|
|
|
|
|
|
|
}
|
253
|
0
|
0
|
|
|
|
|
$dbh->disconnect() unless $warnmsg eq "";
|
254
|
|
|
|
|
|
|
#Todo: warnmsg needs to be returned to the user?
|
255
|
|
|
|
|
|
|
}
|
256
|
0
|
|
|
|
|
|
return $dbh->FETCH('Active');
|
257
|
|
|
|
|
|
|
}
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub prepare {
|
260
|
0
|
|
|
0
|
|
|
my ($dbh, $statement, @attribs) = @_;
|
261
|
0
|
0
|
|
|
|
|
return $drh->set_err(1, 'Statement preparation failed: '.
|
262
|
|
|
|
|
|
|
'There is no active database connection.')
|
263
|
|
|
|
|
|
|
unless $dbh->FETCH('Active');
|
264
|
0
|
0
|
|
|
|
|
return $drh->set_err(1, 'Statement preparation failed: '.
|
265
|
|
|
|
|
|
|
'The sql statement is empty.') unless length($statement);
|
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# workaround for a peculiarity of the ftp server: if CR/LF is present
|
268
|
|
|
|
|
|
|
# the preceding character will be removed (the string will be chopped)
|
269
|
0
|
|
|
|
|
|
$statement =~ s/\r|\n/ /g;
|
270
|
|
|
|
|
|
|
# create a 'blank' sth
|
271
|
0
|
|
|
|
|
|
my ($outer, $sth) = DBI::_new_sth($dbh, {
|
272
|
|
|
|
|
|
|
Statement => $statement
|
273
|
|
|
|
|
|
|
});
|
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# Todo: improve the placeholder management
|
276
|
0
|
|
|
|
|
|
$sth->STORE('NUM_OF_PARAMS', ($statement =~ tr/?//));
|
277
|
0
|
|
|
|
|
|
$sth->{mvs_ftpsql_params} = [];
|
278
|
|
|
|
|
|
|
|
279
|
0
|
|
|
|
|
|
return $outer;
|
280
|
|
|
|
|
|
|
}
|
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub commit {
|
283
|
0
|
|
|
0
|
|
|
my ($dbh) = @_;
|
284
|
0
|
0
|
|
|
|
|
if ($dbh->FETCH('Warn')) {
|
285
|
0
|
|
|
|
|
|
warn("Commit ineffective while AutoCommit is on");
|
286
|
|
|
|
|
|
|
}
|
287
|
0
|
|
|
|
|
|
0;
|
288
|
|
|
|
|
|
|
}
|
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub rollback {
|
291
|
0
|
|
|
0
|
|
|
my ($dbh) = @_;
|
292
|
0
|
0
|
|
|
|
|
if ($dbh->FETCH('Warn')) {
|
293
|
0
|
|
|
|
|
|
warn("Rollback ineffective while AutoCommit is on");
|
294
|
|
|
|
|
|
|
}
|
295
|
0
|
|
|
|
|
|
0;
|
296
|
|
|
|
|
|
|
}
|
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub STORE {
|
299
|
0
|
|
|
0
|
|
|
my ($dbh, $attr, $val) = @_;
|
300
|
0
|
0
|
|
|
|
|
if ($attr eq 'AutoCommit') {
|
301
|
0
|
0
|
|
|
|
|
if (!$val) { die "Can't disable AutoCommit"; }
|
|
0
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
|
return 1;
|
303
|
|
|
|
|
|
|
}
|
304
|
0
|
0
|
|
|
|
|
if ($attr eq 'ChopBlanks') {
|
305
|
0
|
0
|
|
|
|
|
if (!$val) { die "Can't set ChopBlanks to false"; }
|
|
0
|
|
|
|
|
|
|
306
|
0
|
|
|
|
|
|
return 1;
|
307
|
|
|
|
|
|
|
}
|
308
|
0
|
0
|
|
|
|
|
if ($attr eq 'Active') {
|
309
|
0
|
|
|
|
|
|
die "Can't change the read-only connection status attribute 'Active'";
|
310
|
0
|
|
|
|
|
|
return 1;
|
311
|
|
|
|
|
|
|
}
|
312
|
0
|
0
|
|
|
|
|
if ($attr =~ m/^mvs_ftpsql_/) {
|
313
|
0
|
|
|
|
|
|
$dbh->{$attr} = $val;
|
314
|
0
|
|
|
|
|
|
return 1;
|
315
|
|
|
|
|
|
|
}
|
316
|
0
|
|
|
|
|
|
$dbh->SUPER::STORE($attr, $val);
|
317
|
|
|
|
|
|
|
}
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub FETCH {
|
320
|
0
|
|
|
0
|
|
|
my ($dbh, $attr) = @_;
|
321
|
0
|
0
|
|
|
|
|
if ($attr eq 'AutoCommit') { return 1; }
|
|
0
|
|
|
|
|
|
|
322
|
0
|
0
|
|
|
|
|
if ($attr eq 'ChopBlanks') { return 1; }
|
|
0
|
|
|
|
|
|
|
323
|
0
|
0
|
|
|
|
|
if ($attr eq 'Active') {
|
324
|
0
|
|
0
|
|
|
|
return defined($dbh->{mvs_ftpsql_connection})
|
325
|
|
|
|
|
|
|
&& defined($dbh->{mvs_ftpsql_connection}->connected());
|
326
|
|
|
|
|
|
|
}
|
327
|
0
|
0
|
|
|
|
|
if ($attr =~ m/^mvs_ftpsql_/) {
|
328
|
0
|
|
|
|
|
|
return $dbh->{$attr};
|
329
|
|
|
|
|
|
|
}
|
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# defined($conn->connected());
|
332
|
|
|
|
|
|
|
|
333
|
0
|
|
|
|
|
|
$dbh->SUPER::FETCH($attr);
|
334
|
|
|
|
|
|
|
}
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub disconnect () {
|
337
|
0
|
|
|
0
|
|
|
my $dbh = shift;
|
338
|
0
|
0
|
|
|
|
|
$dbh->{mvs_ftpsql_connection}->quit() if $dbh->FETCH('Active');
|
339
|
|
|
|
|
|
|
#$dbh->STORE('Active',0);
|
340
|
0
|
|
|
|
|
|
return 1;
|
341
|
|
|
|
|
|
|
}
|
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub DESTROY ($) {
|
344
|
0
|
|
|
0
|
|
|
my $dbh = shift;
|
345
|
|
|
|
|
|
|
#Take care of DBI handle 0x....... cleared whilst still active error.
|
346
|
0
|
|
|
|
|
|
$dbh->disconnect();
|
347
|
|
|
|
|
|
|
}
|
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
#End of DBD::MVS_FTPSQL::db
|
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
package DBD::MVS_FTPSQL::st;
|
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
$DBD::MVS_FTPSQL::st::imp_data_size = 0;
|
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
#Attributes Implemented
|
356
|
|
|
|
|
|
|
#NUM_OF_FIELDS (integer, read-only)
|
357
|
|
|
|
|
|
|
#NAME (array-ref, read-only)
|
358
|
|
|
|
|
|
|
#NAME_lc (array-ref, read-only)
|
359
|
|
|
|
|
|
|
#NAME_uc (array-ref, read-only)
|
360
|
|
|
|
|
|
|
#NAME_hash (hash-ref, read-only)
|
361
|
|
|
|
|
|
|
#NAME_lc_hash (hash-ref, read-only)
|
362
|
|
|
|
|
|
|
#NAME_uc_hash (hash-ref, read-only)
|
363
|
|
|
|
|
|
|
#Statement (string, read-only)
|
364
|
|
|
|
|
|
|
#Database (dbh, read-only)
|
365
|
|
|
|
|
|
|
#Attributes not Implemented (todo)
|
366
|
|
|
|
|
|
|
#TYPE (array-ref, read-only)
|
367
|
|
|
|
|
|
|
#PRECISION (array-ref, read-only)
|
368
|
|
|
|
|
|
|
#SCALE (array-ref, read-only)
|
369
|
|
|
|
|
|
|
#NULLABLE (array-ref, read-only)
|
370
|
|
|
|
|
|
|
#CursorName (string, read-only)
|
371
|
|
|
|
|
|
|
#ParamValues (hash ref, read-only)
|
372
|
|
|
|
|
|
|
#ParamArrays (hash ref, read-only)
|
373
|
|
|
|
|
|
|
#ParamTypes (hash ref, read-only)
|
374
|
|
|
|
|
|
|
#RowsInCache (integer, read-only)
|
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
sub STORE {
|
377
|
0
|
|
|
0
|
|
|
my ($sth, $attr, $val) = @_;
|
378
|
0
|
0
|
|
|
|
|
if ($attr =~ m/^mvs_ftpsql_/) {
|
379
|
0
|
|
|
|
|
|
$sth->{$attr} = $val;
|
380
|
0
|
|
|
|
|
|
return 1;
|
381
|
|
|
|
|
|
|
}
|
382
|
0
|
|
|
|
|
|
$sth->SUPER::STORE($attr, $val);
|
383
|
|
|
|
|
|
|
}
|
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
sub FETCH {
|
386
|
0
|
|
|
0
|
|
|
my ($sth, $attr) = @_;
|
387
|
0
|
0
|
|
|
|
|
if ($attr =~ m/^mvs_ftpsql_/) {
|
388
|
0
|
|
|
|
|
|
return $sth->{$attr};
|
389
|
|
|
|
|
|
|
}
|
390
|
0
|
|
|
|
|
|
$sth->SUPER::FETCH($attr);
|
391
|
|
|
|
|
|
|
}
|
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
#Taken (like other pieces of code) from DBI guide
|
394
|
|
|
|
|
|
|
sub bind_param {
|
395
|
0
|
|
|
0
|
|
|
my ($sth, $pNum, $val, $attr) = @_;
|
396
|
0
|
0
|
|
|
|
|
my $type = (ref $attr) ? $attr->{TYPE} : $attr;
|
397
|
0
|
0
|
|
|
|
|
if ($type) {
|
398
|
0
|
|
|
|
|
|
my $dbh = $sth->{Database};
|
399
|
|
|
|
|
|
|
#mhm seems a bug in the manual?
|
400
|
|
|
|
|
|
|
#$val = $dbh->quote($sth, $type);
|
401
|
0
|
|
|
|
|
|
$val = $dbh->quote($val, $type);
|
402
|
|
|
|
|
|
|
}
|
403
|
0
|
|
|
|
|
|
my $params = $sth->{mvs_ftpsql_params};
|
404
|
0
|
|
|
|
|
|
$params->[$pNum-1] = $val;
|
405
|
0
|
|
|
|
|
|
1;
|
406
|
|
|
|
|
|
|
}
|
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
sub execute {
|
409
|
0
|
|
|
0
|
|
|
my ($sth, @bind_values) = @_;
|
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
# start of by finishing any previous execution if still active
|
412
|
0
|
0
|
|
|
|
|
$sth->finish if $sth->FETCH('Active');
|
413
|
0
|
0
|
|
|
|
|
my $params = (@bind_values) ?
|
414
|
|
|
|
|
|
|
\@bind_values : $sth->{mvs_ftpsql_params};
|
415
|
|
|
|
|
|
|
|
416
|
0
|
|
|
|
|
|
my $numParam = $sth->FETCH('NUM_OF_PARAMS');
|
417
|
0
|
0
|
|
|
|
|
return $sth->set_err(1, "Wrong number of parameters")
|
418
|
|
|
|
|
|
|
if @$params != $numParam;
|
419
|
|
|
|
|
|
|
|
420
|
0
|
|
|
|
|
|
my $statement = $sth->{'Statement'};
|
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
#Todo: the bind mechanism needs to be improved
|
423
|
0
|
|
|
|
|
|
for (my $i = 0; $i < $numParam; $i++) {
|
424
|
0
|
|
|
|
|
|
$statement =~ s/\?/$params->[$i]/;
|
425
|
|
|
|
|
|
|
}
|
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
#very dirty error handling technique, but eval {} if(@$) seems to clutter
|
428
|
|
|
|
|
|
|
#(maybe my mistake) with $drh->set_err (todo: dig into the problem)
|
429
|
0
|
|
|
|
|
|
my ($error_code,$error_message,$error_state) = (1,"",0);
|
430
|
0
|
|
|
|
|
|
my $dbh = $sth->{Database};
|
431
|
0
|
0
|
|
|
|
|
my $fh = mvs_ftpsql_execute(
|
432
|
|
|
|
|
|
|
$dbh->{'mvs_ftpsql_connection'}
|
433
|
|
|
|
|
|
|
,$dbh->{'mvs_ftpsql_ssid'}
|
434
|
|
|
|
|
|
|
,$dbh->{'mvs_ftpsql_remote_prefix'}
|
435
|
|
|
|
|
|
|
,$statement
|
436
|
|
|
|
|
|
|
,\$error_message
|
437
|
|
|
|
|
|
|
,\$error_state
|
438
|
|
|
|
|
|
|
,\$error_code
|
439
|
|
|
|
|
|
|
) or return $sth->set_err($error_code, $error_message,$error_state);
|
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
# Notice that this driver processes only SELECT statement (a protocol
|
442
|
|
|
|
|
|
|
# limitation imposed by design), so $fh is ever a file handle to the
|
443
|
|
|
|
|
|
|
# output of a query.
|
444
|
|
|
|
|
|
|
#print while(<$fh>);exit;
|
445
|
0
|
|
|
|
|
|
my $header = <$fh>;
|
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
#\x00 was placed as a workaround for a strange behaviour with some tables
|
448
|
0
|
|
|
|
|
|
$header =~ s/\x00| |\r|\n//g;
|
449
|
0
|
|
|
|
|
|
my @header = split(/\t/,$header);
|
450
|
|
|
|
|
|
|
#print $header[0];exit;
|
451
|
0
|
0
|
|
|
|
|
unless (exists($sth->{'NAME'})) {
|
452
|
0
|
|
|
|
|
|
$sth->STORE('NUM_OF_FIELDS' => $#header +1);
|
453
|
0
|
|
|
|
|
|
$sth->{'NAME'} = \@header;
|
454
|
|
|
|
|
|
|
}
|
455
|
|
|
|
|
|
|
|
456
|
0
|
|
|
|
|
|
$sth->{'mvs_ftpsql_data'} = $fh;
|
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
#Row counting
|
459
|
0
|
|
|
|
|
|
my $rowcount = 0;
|
460
|
0
|
|
|
|
|
|
my $pos = $fh->getpos();
|
461
|
0
|
|
|
|
|
|
$rowcount++ while(<$fh>);
|
462
|
0
|
|
|
|
|
|
$fh->setpos($pos);
|
463
|
0
|
|
|
|
|
|
$sth->{'mvs_ftpsql_rows'} = $rowcount;
|
464
|
|
|
|
|
|
|
|
465
|
0
|
|
|
|
|
|
$sth->{Active} = 1;
|
466
|
0
|
0
|
|
|
|
|
return ($rowcount ? $rowcount : '0E0');
|
467
|
|
|
|
|
|
|
}
|
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
sub fetchrow_arrayref {
|
470
|
0
|
|
|
0
|
|
|
my ($sth) = @_;
|
471
|
0
|
|
|
|
|
|
my $fh = $sth->{mvs_ftpsql_data};
|
472
|
0
|
0
|
|
|
|
|
unless ($fh) {
|
473
|
0
|
|
|
|
|
|
$sth->STORE(Active => 0);
|
474
|
0
|
|
|
|
|
|
return undef;
|
475
|
|
|
|
|
|
|
}
|
476
|
0
|
|
|
|
|
|
my $tmp = <$fh>;
|
477
|
0
|
0
|
|
|
|
|
unless ($tmp) {
|
478
|
0
|
|
|
|
|
|
$sth->STORE(Active => 0);
|
479
|
0
|
|
|
|
|
|
return undef;
|
480
|
|
|
|
|
|
|
}
|
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
#Text fields are right padded, numbers are left padded.
|
483
|
|
|
|
|
|
|
#The field is at least long as his label.
|
484
|
|
|
|
|
|
|
#This is the reason we can't disable ChopBlanks
|
485
|
0
|
|
|
|
|
|
$tmp =~ s/\r|\n//g;
|
486
|
0
|
|
|
|
|
|
$tmp =~ s/ +\t/\t/g;
|
487
|
0
|
|
|
|
|
|
$tmp =~ s/ +$//g;
|
488
|
|
|
|
|
|
|
|
489
|
0
|
|
|
|
|
|
my @fields = split(/\t/,$tmp,-1);
|
490
|
0
|
0
|
|
|
|
|
if (($sth->FETCH('NUM_OF_FIELDS')) < ($#fields+1) ) {
|
491
|
0
|
|
|
|
|
|
$fh->close();
|
492
|
0
|
|
|
|
|
|
$sth->SUPER::finish();
|
493
|
|
|
|
|
|
|
#Todo: give more info in the pod and propose as solution
|
494
|
|
|
|
|
|
|
#TRANSLATE (A, ' ', x'05') (lo horizontal tab (HT) \x09 in EBCDIC diventa \x05)
|
495
|
0
|
|
|
|
|
|
Carp::croak (
|
496
|
|
|
|
|
|
|
"Fetch failed: Horizontal tab found. One or more character columns in the resultset ".
|
497
|
|
|
|
|
|
|
"contain tabs characters ('\\x09').\nAlthough not an error, due to ".
|
498
|
|
|
|
|
|
|
"limitations imposed by the ftp/sql feature this driver can't ".
|
499
|
|
|
|
|
|
|
"manage those values.\nSee the documentation to learn how to work ".
|
500
|
|
|
|
|
|
|
"around this issue."
|
501
|
|
|
|
|
|
|
);
|
502
|
|
|
|
|
|
|
}
|
503
|
0
|
|
|
|
|
|
return $sth->_set_fbav(\@fields);
|
504
|
|
|
|
|
|
|
}
|
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
*fetch = \&fetchrow_arrayref; # required alias for fetchrow_arrayref
|
507
|
|
|
|
|
|
|
|
508
|
0
|
|
|
0
|
|
|
sub rows { shift->{mvs_ftpsql_rows}; }
|
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
sub DESTROY {
|
511
|
0
|
|
|
0
|
|
|
my $sth = shift;
|
512
|
0
|
0
|
|
|
|
|
$sth->finish if $sth->FETCH('Active');
|
513
|
|
|
|
|
|
|
}
|
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
sub finish {
|
516
|
0
|
|
|
0
|
|
|
my $sth = shift;
|
517
|
0
|
|
|
|
|
|
$sth->{mvs_ftpsql_data}->close();
|
518
|
0
|
|
|
|
|
|
$sth->SUPER::finish();
|
519
|
|
|
|
|
|
|
}
|
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
sub mvs_ftpsql_execute {
|
522
|
0
|
|
|
0
|
|
|
my $ftp_conn = shift;
|
523
|
0
|
|
|
|
|
|
my $db2subsys = shift;
|
524
|
0
|
|
|
|
|
|
my $remote_sql_filename_prefix = shift;
|
525
|
0
|
|
|
|
|
|
my $sql = shift;
|
526
|
0
|
|
|
|
|
|
my $error_message = shift;
|
527
|
0
|
|
|
|
|
|
my $error_state = shift;
|
528
|
0
|
|
|
|
|
|
my $error_code = shift;
|
529
|
0
|
|
|
|
|
|
my $qlen = length($sql);
|
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
#datasets allocated with RETPD > 0 can't be deleted
|
532
|
0
|
|
|
|
|
|
$ftp_conn->quot("site FILE=SEQ LR=$qlen BLOCKSI=$qlen REC=F RET=0");
|
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
#Query upload
|
535
|
0
|
0
|
|
|
|
|
my $fh = IO::File->new_tmpfile()
|
536
|
|
|
|
|
|
|
or Carp::croak("Cannot create temporary storage for the sql statement:$!");
|
537
|
0
|
0
|
|
|
|
|
$fh->seek(0,0) || Carp::croak ("Seek operation failed:$!");
|
538
|
0
|
|
|
|
|
|
print $fh $sql;
|
539
|
0
|
0
|
|
|
|
|
$fh->flush() || Carp::croak ("Flush operation failed:$!");
|
540
|
0
|
0
|
|
|
|
|
$fh->seek(0,0) || Carp::croak ("Seek operation failed:$!");
|
541
|
0
|
|
|
|
|
|
$ftp_conn->put_unique($fh,$remote_sql_filename_prefix.'0001');
|
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
#Workaround:the current implementation of Net::FTP::put_unique do not
|
544
|
|
|
|
|
|
|
#returns the filename. The error lie in the regexp at line 72 of
|
545
|
|
|
|
|
|
|
#Net/FTP/dataconn.pm
|
546
|
0
|
0
|
0
|
|
|
|
my $filename = $1
|
547
|
|
|
|
|
|
|
if $ftp_conn->message() =~
|
548
|
|
|
|
|
|
|
/($remote_sql_filename_prefix\d{4}) \(unique name\)/
|
549
|
|
|
|
|
|
|
or Carp::croak ("Cannot determine the remote sql filename.");
|
550
|
0
|
|
|
|
|
|
$ftp_conn->quot ('SITE NOTRAIL FILE=SQL DB2='.$db2subsys.' SPR LR=32000 REC=F '.
|
551
|
|
|
|
|
|
|
'SQLC=N BLOCKSI=32000');
|
552
|
0
|
0
|
|
|
|
|
$fh->truncate(0) || Carp::croak ("Truncate operation failed:$!");
|
553
|
|
|
|
|
|
|
#Error handling
|
554
|
|
|
|
|
|
|
#"551 Transfer aborted: SQL PREPARE/DESCRIBE failure" -> sql syntax error
|
555
|
|
|
|
|
|
|
#"551 Transfer aborted: SQL not available. Attempt to open plan EZAFTPMQ"
|
556
|
|
|
|
|
|
|
#"554 Transfer aborted: unsupported SQL statement" -> only selects
|
557
|
|
|
|
|
|
|
#"551 Transfer aborted: attempt to connect to DB2 failed" -> subsystem error
|
558
|
|
|
|
|
|
|
#MVS was unable to locate a DB2 subsystem with the specified name
|
559
|
|
|
|
|
|
|
|
560
|
0
|
|
|
|
|
|
my $warnmsg = "";
|
561
|
0
|
|
|
|
|
|
my $transfer_msg="";
|
562
|
|
|
|
|
|
|
{
|
563
|
0
|
|
|
0
|
|
|
local $SIG{__WARN__} = sub {$warnmsg=shift;};
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
564
|
0
|
|
|
|
|
|
$ftp_conn->get ($filename,$fh);
|
565
|
0
|
|
|
|
|
|
$transfer_msg = $ftp_conn->message();
|
566
|
|
|
|
|
|
|
}
|
567
|
0
|
|
|
|
|
|
$ftp_conn->quot ('SITE FILETYPE=SEQ');
|
568
|
0
|
|
|
|
|
|
$ftp_conn->delete($filename);
|
569
|
0
|
0
|
|
|
|
|
if ($transfer_msg =~ /Transfer aborted: SQL PREPARE\/DESCRIBE failure/) {
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
570
|
0
|
0
|
|
|
|
|
$fh->flush() || Carp::croak ("Flush operation failed:$!");
|
571
|
0
|
0
|
|
|
|
|
$fh->seek(0,0) ||Carp::croak ("Seek operation failed:$!");
|
572
|
|
|
|
|
|
|
|
573
|
0
|
|
|
|
|
|
$$error_message = "The SQL statement is invalid:\n". do {local $/; <$fh>} ."\n";
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
#Workaround for a problem with filehandles and set_err
|
576
|
|
|
|
|
|
|
#Forces a copy of the content of the file.
|
577
|
|
|
|
|
|
|
#Without the following line the content of the error message is not reported.
|
578
|
0
|
|
|
|
|
|
$$error_message = sprintf ('%s',$$error_message);
|
579
|
0
|
0
|
|
|
|
|
$$error_state = $1 if ($$error_message =~ /SQLSTATE\s+=\s+(\d+)/);
|
580
|
0
|
0
|
|
|
|
|
$$error_code = $1 if ($$error_message =~ /SQLCODE\s+=\s+([\-0-9]+)/);
|
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
#print "$$error_state";
|
583
|
|
|
|
|
|
|
#exit;
|
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
} elsif($transfer_msg =~ /Transfer aborted: (.*)/) {
|
586
|
0
|
|
|
|
|
|
$$error_message = $1;
|
587
|
0
|
|
|
|
|
|
$$error_code = '-30080';
|
588
|
0
|
|
|
|
|
|
$$error_state = '08001'
|
589
|
|
|
|
|
|
|
} elsif ($warnmsg ne "") {
|
590
|
0
|
|
|
|
|
|
$$error_message = $warnmsg;
|
591
|
0
|
|
|
|
|
|
$$error_code = '-30080';
|
592
|
0
|
|
|
|
|
|
$$error_state = '08001'
|
593
|
|
|
|
|
|
|
} else {
|
594
|
0
|
0
|
|
|
|
|
$fh->seek(0,0) || Carp::croak ("Seek operation failed:$!");
|
595
|
0
|
|
|
|
|
|
return $fh;
|
596
|
|
|
|
|
|
|
}
|
597
|
0
|
0
|
|
|
|
|
$fh->flush() || Carp::croak ("Flush operation failed:$!");
|
598
|
0
|
|
|
|
|
|
$fh->close();
|
599
|
0
|
|
|
|
|
|
undef ($fh);
|
600
|
0
|
|
|
|
|
|
return undef;
|
601
|
|
|
|
|
|
|
}
|
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
#End of DBD::MVS_FTPSQL::st
|
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
1;
|
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
__END__
|