File Coverage

blib/lib/DBIx/BlackBox.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package DBIx::BlackBox;
2              
3 1     1   30314 use MooseX::Role::Parameterized;
  0            
  0            
4              
5             use DBIx::Connector;
6             use DBI;
7             use Module::Find qw( findallmod );
8              
9             =encoding utf8
10              
11             =head1 NAME
12              
13             DBIx::BlackBox - access database with stored procedures only
14              
15             =cut
16              
17             our $VERSION = '0.02';
18              
19             =head1 SYNOPSIS
20              
21             L<DBIx::BlackBox> provides access to database using stored procedures only
22             (the only SQL command available is I<exec>). That allows to treat your
23             database as a black box into which only the database administrator provides
24             access by stored procedures.
25              
26             Setup base class:
27              
28             package MyDBBB;
29             use Moose;
30              
31             with 'DBIx::BlackBox' => {
32             connect_info => [
33             'dbi:Sybase:server=sqlserver',
34             'username',
35             'password',
36             {
37             RaiseError = 1,
38             PrintError = 0,
39             }
40             ]
41             };
42              
43             Create procedures classes. Attributes define stored procedure parameters.
44              
45             package MyDBBB::Procedures::ListCatalogs;
46             use Moose;
47              
48             with 'DBIx::BlackBox::Procedure' => {
49             name => 'DB_Live..list_catalogs',
50             resultsets => [qw(
51             MyDBBB::ResultSet::Catalogs
52             MyDBBB::ResultSet::CatalogData
53             )],
54             };
55              
56             has 'root_id' => (
57             is => 'rw',
58             isa => 'Int',
59             required => 1,
60             );
61             has 'org_id' => (
62             is => 'rw',
63             isa => 'Maybe[Int]',
64             );
65              
66             package MyDBBB::Procedures::UpdateCatalog;
67             use Moose;
68              
69             with 'DBIx::BlackBox::Procedure' => {
70             name => 'DB_Live..update_catalog',
71             };
72              
73             has 'id' => (
74             is => 'rw',
75             isa => 'Int',
76             required => 1,
77             );
78             has 'name' => (
79             is => 'rw',
80             isa => 'Str',
81             required => 1,
82             );
83              
84             Describe result sets for procedures. They could (and should) be shared between
85             procedures.
86              
87             package MyDBBB::ResultSet::Catalogs;
88             use Moose;
89              
90             has 'id' => (
91             is => 'rw',
92             isa => 'Int',
93             );
94             has 'name' => (
95             is => 'rw',
96             isa => 'Str',
97             );
98              
99             package MyDBBB::ResultSet::CatalogData;
100             use Moose;
101              
102             has 'id' => (
103             is => 'rw',
104             isa => 'Int',
105             );
106             has 'hierarchy' => (
107             is => 'rw',
108             isa => 'Int',
109             );
110             has 'description' => (
111             is => 'rw',
112             isa => 'Str',
113             );
114              
115             and then
116              
117             use MyDBBB;
118              
119             my $dbbb = MyDBBB->new();
120              
121             execute stored procedure and get result object to iterate over
122              
123             my $rs = eval {
124             $dbbb->exec('ListCatalogs',
125             root_id => $root_id,
126             org_id => $org_id,
127             );
128             } or do {
129             die $@;
130             }
131              
132             my @columns = (
133             [qw( id name )],
134             [qw( id hierarchy description )],
135             );
136             do {
137             my @c = @{ $columns[ $rs->idx ] };
138             while ( my $row = $rs->next_row ) {
139             print "$_: ", $row->$_, "\n"
140             for @c;
141             }
142             } while ( $rs->next_resultset );
143              
144             print "procedure_result: ", $rs->procedure_result, "\n";
145              
146             or get all rows at once
147            
148             my ( $catalogs, $data, $rv ) = $dbbb->exec('ListCatalogs',
149             root_id => $root_id,
150             org_id => $org_id,
151             )->all;
152              
153             for my $catalog ( @$catalogs ) {
154             print $catalog->id, ": ", $catalog->name, "\n";
155             }
156              
157             for my $row ( @$data ) {
158             print $row->id, "[", $row->hierarchy, "]: ", $row->description, "\n";
159             }
160              
161             print "procedure result: $rv";
162              
163             =head1 ROLE PARAMETERS
164              
165             =head2 connect_info
166              
167             Database connection arguments passed to L<DBI/"connect">.
168              
169             Required.
170              
171             Note: currently only DBD::Sybase (MS SQL Server) is supported.
172              
173             =cut
174              
175             parameter connect_info => (
176             isa => 'ArrayRef',
177             required => 1,
178             );
179              
180             =head2 procedures_namespace
181              
182             All classes in provided namespace them will be automatically loaded.
183              
184             Defaults to name of the consumer of DBIx::BlackBox role with C<::Procedures>
185             appended.
186              
187             Note: those classes need to consume DBIx::BlackBox::Procedure role.
188              
189             =cut
190              
191             parameter procedures_namespace => (
192             isa => 'Str',
193             );
194              
195             =head1 ATTRIBUTES
196              
197             =head2 connect_info
198              
199             Returns the value of role parameter L<"connect_info">.
200              
201             =head2 procedures_namespace
202              
203             Returns the value of role parameter L<"procedures_namespace">.
204              
205             =head1 METHODS
206              
207             =head2 exec
208              
209             my $rs = $dbbb->exec($procedure_class, %args);
210              
211             Instantiates an object of the C<$procedure_class> (which is appended to
212             L<"procedures_namespaces">) with arguments provided by C<%args> and executes
213             procedure defined by class.
214              
215             Procedures should used named paremeters only.
216              
217             =cut
218              
219             =head1 INSTALLATION
220              
221             Following installation steps were tested with both
222             Microsoft SQL Server 2000 and Microsoft SQL Server 2008.
223              
224             =head2 unixODBC
225              
226             Install unixODBC from your system packages or download sources from
227             L<http://www.unixodbc.org/>.
228              
229             =head2 FreeTDS
230              
231             Download dev release of FreeTDS from L<http://www.freetds.org> (tested with
232             freetds-0.83.dev.20100122).
233              
234             ./configure --with-unixodbc=/usr/local/ \
235             --with-tdsver=8.0 --prefix=/usr/local/freetds
236             make
237             sudo make install
238              
239             Edit F</usr/local/freetds/etc/freetds.conf> and specify access to your
240             database.
241              
242             ...
243             [sqlserver]
244             host = 1.2.3.4
245             port = 1433
246             tds version = 8.0
247              
248             =head2 DBD::Sybase
249              
250             Install L<DBD::Sybase>.
251              
252             SYBASE=/usr/local/freetds perl Makefile.PL
253             make
254             sudo make install
255              
256             If you want to test DBD::Sybase most likely you would need to modify tests
257             that come with the module (some queries in test files will not work with
258             MS SQL Server).
259              
260             =cut
261              
262             role {
263             my $p = shift;
264             my %args = @_;
265             my $consumer = $args{consumer};
266              
267             my $_proc_class_ns = $p->procedures_namespace ?
268             $p->procedures_namespace
269             :
270             join('::', $consumer->name, 'Procedures' );
271              
272             {
273             my @mods = findallmod( $_proc_class_ns );
274             do {
275             my $proc_class = $_;
276              
277             Class::MOP::load_class( $proc_class );
278             my $proc_meta = $proc_class->meta;
279              
280             my $proc_role = 'DBIx::BlackBox::Procedure';
281             unless ( $proc_meta->does_role($proc_role) ) {
282             die "Class $proc_class does not consume $proc_role role\n";
283             }
284             } for @mods;
285             }
286              
287             has 'connect_info' => (
288             traits => [qw( Array )],
289             is => 'ro',
290             isa => 'ArrayRef',
291             default => sub {
292             $p->connect_info,
293             },
294             handles => {
295             _db_connection_params => 'elements',
296             }
297             );
298              
299             has '_conn' => (
300             is => 'rw',
301             isa => 'DBIx::BlackBox::Driver',
302             lazy_build => 1,
303             );
304              
305             has 'procedures_namespace' => (
306             is => 'ro',
307             isa => 'Str',
308             default => $_proc_class_ns,
309             );
310              
311             method '_build__conn' => sub {
312             my $self = shift;
313              
314             my @coninfo = $self->_db_connection_params;
315              
316             my $dsn = $coninfo[0];
317             my (undef, $driver) = DBI->parse_dsn( $dsn );
318              
319             my $db_class = "DBIx::BlackBox::Driver::$driver";
320             Class::MOP::load_class( $db_class );
321              
322             return $db_class->new(
323             connector => DBIx::Connector->new( @coninfo )
324             );
325             };
326              
327             method 'exec' => sub {
328             my ($self, $name, %args) = @_;
329              
330             my $proc_class = join('::', $self->procedures_namespace, $name );
331              
332             my $proc = $proc_class->new( %args );
333              
334             return $proc->exec( $self->_conn );
335             }
336             };
337              
338             no MooseX::Role::Parameterized;
339              
340             =head1 CAVEATS
341              
342             Neither the stored procedures nor result sets classes can have
343             attributes/columns/parameters that would clash with Moose internals,
344             e.g. I<new>.
345              
346             =head1 AUTHOR
347              
348             Alex J. G. BurzyÅ„ski, E<lt>ajgb at cpan.orgE<gt>
349              
350             =head1 BUGS
351              
352             Please report any bugs or feature requests to C<bug-dbix-blackbox at rt.cpan.org>, or through
353             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=DBIx-BlackBox>. I will be notified, and then you'll
354             automatically be notified of progress on your bug as I make changes.
355              
356             =head1 LICENSE AND COPYRIGHT
357              
358             Copyright 2010 Alex J. G. BurzyÅ„ski.
359              
360             This program is free software; you can redistribute it and/or modify it
361             under the terms of either: the GNU General Public License as published
362             by the Free Software Foundation; or the Artistic License.
363              
364             See http://dev.perl.org/licenses/ for more information.
365              
366             =cut
367              
368             1; # End of DBIx::BlackBox