File Coverage

blib/lib/DBIx/Librarian.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package DBIx::Librarian;
2              
3             require 5.005;
4 1     1   142235 use strict;
  1         3  
  1         49  
5             #use warnings; # needs 5.6
6 1     1   7 use vars qw($VERSION);
  1         3  
  1         72  
7              
8             $VERSION = '0.6';
9              
10 1     1   1905 use Data::Library::OnePerFile; # default archiver
  0            
  0            
11             use DBIx::Librarian::Statement;
12              
13             =head1 NAME
14              
15             DBIx::Librarian - Manage SQL in repository outside code
16              
17             =head1 SYNOPSIS
18              
19             use DBIx::Librarian;
20              
21             my $dblbn = new DBIx::Librarian;
22              
23             my $data = { id => 473 };
24             eval { $dblbn->execute("lookup_employee", $data); };
25             die $@ if $@;
26             print "Employee $data->{id} is $data->{name}\n";
27              
28             $dblbn->disconnect;
29              
30             =head1 OBJECTIVES
31              
32             Separation of database logic from application logic (SQL from Perl)
33              
34             Simple interface - sacrifices some flexibility in exchange for
35             code readability and development speed
36              
37             Leave SQL syntax untouched if possible; support any extensions that are
38             supported by the underlying database
39              
40             Support transaction capability if the database allows it
41              
42             This is NOT an object-to-relational-mapping toolkit or a persistence
43             framework. For that sort of thing, see SPOPS or any of several other
44             excellent modules. The combination of DBIx::Librarian and Template
45             Toolkit or one of the other templating packages will give the basis
46             of a fairly comprehensive database-driven application framework.
47              
48             =head1 FEATURES
49              
50             =over
51              
52             =item *
53              
54             Support full complexity of Perl associative data structures
55              
56             =item *
57              
58             Multiple SQL statements chained in a single execute() invocation.
59             Use results from one call as inputs to the next.
60              
61             =item *
62              
63             Each execute() is automatically a transaction, comprising one or
64             more statements per the above. Optional delayed commit to
65             collect multiple invocations into a transaction. Note that if your
66             database doesn't support transactions (e.g. vanilla mySQL), then
67             you're still out of luck here.
68              
69             =item *
70              
71             Processing modes for select statements: exactly one row, zero-or-one,
72             multiple rows (zero to many); optional exception on receiving multiple
73             rows when expecting just one. SQL syntax is extended to provide these
74             controls.
75              
76             =item *
77              
78             Support bind variables, and on-the-fly SQL generation through substitution
79             of entire SQL fragments.
80              
81             =item *
82              
83             Supports multiple repositories for queries - currently supports
84             individual files and multiple-query files.
85              
86             =item *
87              
88             Database connection can be passed into the Librarian initializer, or
89             it will create it internally.
90              
91             =item *
92              
93             If the database connection is down when execute() is called, Librarian
94             will attempt to re-connect.
95              
96             =item *
97              
98             Sets DBI LongReadLen and LongTruncOk to allow for fetching long values.
99             Optional LONGREADLEN parameter to DBIx::Librarian::new will be passed
100             through to DBI (default 1000).
101              
102             =back
103              
104             =head1 ENVIRONMENT VARIABLES
105              
106             DBIx::Librarian will use the following:
107              
108             DBI_DSN standard DBI connection parameters
109             DBI_USER
110             DBI_PASS
111              
112              
113             =head1 DESCRIPTION
114              
115             This is for data manipulation (SELECT, INSERT, UPDATE, DELETE), not for
116             data definition (CREATE, DROP, ALTER). Some DDL statements may work
117             inside this module, but correct behavior is not guaranteed.
118              
119             Results of "SELECT1 colname FROM table", expected to return a single row:
120              
121             {
122             colname => "value"
123             }
124              
125             Access via $data->{colname}
126              
127             If more than one row is returned, raise an exception.
128              
129             Results of "SELECT* colname FROM table", expected to return multiple rows
130             (note alteration to standard SQL syntax):
131              
132             [
133             {
134             colname => "vala"
135             },
136             {
137             colname => "valb"
138             },
139             {
140             colname => "valc"
141             }
142             ]
143              
144             Access via $data->[n]->{colname}
145              
146             Results of "SELECT1 col1, col2 FROM table", expected to return a single row:
147              
148             {
149             col1 => "valA",
150             col2 => "valB",
151             }
152              
153             Access via $data->{colname}
154              
155             If more than one row is returned, raise an exception.
156              
157             Results of
158              
159             SELECT* col1 "record.col1",
160             col2 "record.col2",
161             col3 "record.col3"
162             FROM table
163              
164             expected to return multiple rows:
165              
166             {
167             record =>
168             [
169             {
170             col1 => "val1a",
171             col2 => "val2a",
172             col3 => "val3a"
173             },
174             {
175             col1 => "val1b",
176             col2 => "val2b",
177             col3 => "val3b"
178             },
179             {
180             col1 => "val1c",
181             col2 => "val2c",
182             col3 => "val3c"
183             },
184             ]
185             }
186              
187             Access via $data->{record}[n]->{colname}
188              
189             =head1 TO DO
190              
191             =over
192              
193             =item *
194              
195             Endeavor to consolidate some of this work with other similar modules
196              
197             =item *
198              
199             Optional constraint on number of rows returned by SELECT statements
200              
201             =item *
202              
203             Optional cancellation of long-running queries
204              
205             =item *
206              
207             Verbosity controls for logging during initialization and query execution;
208             tie in with DBI tracing
209              
210             =item *
211              
212             Limits on number of cached statement handles. Some databases may place
213             limits on the number of concurrent handles. Some sort of LRU stack of
214             handles would be useful for this.
215              
216             =item *
217              
218             Consider whether DBI Taint mode would be appropriate here.
219              
220             =item *
221              
222             Make sure this works properly with threads.
223              
224             =item *
225              
226             Improve regex matching for substitution variables in SQL statements so
227             they handle quoting and comments.
228              
229             =item *
230              
231             Additional SQL storage options, e.g. SQL::Catalog (store in a database -
232             should be able to keep SQL in a different database from the app data),
233             Class::Phrasebook::SQL (store in XML).
234              
235             =back
236              
237             =head1 WARNINGS
238              
239             You must call $dblbn->disconnect explicitly before your program terminates.
240              
241             This module uses strict throughout. There is one notable side-effect;
242             if you have a scalar value in a hash element:
243              
244             $data->{name} = "John"
245              
246             and you run a multi-row SELECT with the same field as a target:
247              
248             select* name,
249             department
250             from EMPLOYEE
251              
252             then you are likely to get an error like this:
253              
254             Can't use string ("John") as an ARRAY ref while "strict refs"
255             in use at .../DBIx/Librarian/Statement/SelectMany.pm line XXX.
256              
257             This is because it is trying to write values into
258              
259             $data->{name}[0]
260             $data->{name}[1]
261             etc.
262              
263             Recommended syntax for multi-row, multi-column SELECTs is:
264              
265             select* name "employee.name",
266             department "employee.dept"
267             from EMPLOYEE
268              
269             so then you can access the information via
270              
271             $data->{employee}[0]->{name}
272             $data->{employee}[0]->{dept}
273             $data->{employee}[1]->{name}
274             etc.
275              
276             =head1 METHODS
277              
278             =cut
279              
280             use DBI;
281             use Carp;
282              
283             use Log::Channel;
284              
285             {
286             my $initlog = new Log::Channel ("init");
287             sub initlog { $initlog->(@_) }
288              
289             my $execlog = new Log::Channel ("exec");
290             sub execlog { $execlog->(@_) }
291             }
292              
293             my %select_mode = (
294             "*" => "zero_or_more",
295             "?" => "zero_or_one",
296             "1" => "exactly_one",
297             "" => "zero_or_more",
298             );
299              
300             # defaults
301             my %parameters = (
302             "ARCHIVER" => undef,
303             "LIB" => undef,
304             "EXTENSION" => "sql",
305             "AUTOCOMMIT" => 1,
306             "ALLARRAYS" => 0,
307             "DBH" => undef,
308             "DBI_DSN" => undef,
309             "DBI_USER" => undef,
310             "DBI_PASS" => undef,
311             "LONGREADLEN" => 10000,
312             "MAXSELECTROWS" => 1000,
313             );
314              
315             =item B
316              
317             my $dblbn = new DBIx::Librarian({ name => "value" ... });
318              
319             Supported Librarian parameters:
320              
321             ARCHIVER Reference to class responsible for caching SQL statements.
322             Default is Data::Library::OnePerFile.
323              
324             LIB If set, passed through to archiver
325              
326             EXTENSION If set, passed through to archiver
327              
328             AUTOCOMMIT If set, will commit() upon completion of all the SQL
329             statements in a tag (not after each statement).
330             If not set, the application must call commit() directly.
331             Default is set.
332              
333             ALLARRAYS If set, all bind and direct substition variables will
334             be obtained from element 0 of the named array, rather
335             than from scalars. Default is off.
336              
337             DBH If set, Librarian will use this database handle and
338             will not open one itself.
339              
340             DBI_DSN passed directly to DBI::connect
341             DBI_USER passed directly to DBI::connect
342             DBI_PASS passed directly to DBI::connect
343              
344             LONGREADLEN passed through to "LongReadLen" DBI parameter.
345             Defaults to 10000.
346              
347             MAXSELECTROWS Set to a numeric value. Limits the number of rows returned
348             by a SELECT call. Defaults to 1000.
349              
350             =cut
351              
352             sub new {
353             my ($proto, $config) = @_;
354             my $class = ref ($proto) || $proto;
355              
356             my $self = $config || {};
357              
358             bless ($self, $class);
359              
360             $self->_init;
361              
362             return $self;
363             }
364              
365              
366             sub _init {
367             my ($self) = shift;
368              
369             # verify input params and set defaults
370             # dies on any unknown parameter
371             # fills in the default for anything that is not provided
372              
373             foreach my $key (keys %$self) {
374             if (!exists $parameters{$key}) {
375             croak "Undefined Librarian parameter $key";
376             }
377             }
378              
379             foreach my $key (keys %parameters) {
380             $self->{$key} = $parameters{$key} unless defined $self->{$key};
381             }
382              
383             if (! defined $self->{DBH}) {
384             $self->_connect;
385             }
386              
387             $self->_init_archiver;
388             }
389              
390              
391             sub _init_archiver {
392             my ($self) = shift;
393              
394             my $archiver = $self->{ARCHIVER};
395             my $config = {};
396             $config->{LIB} = $self->{LIB} if $self->{LIB};
397             $config->{EXTENSION} = $self->{EXTENSION} if $self->{EXTENSION};
398              
399             if (!$archiver) {
400             # use default archiver
401              
402             $archiver = new Data::Library::OnePerFile($config);
403             }
404              
405             $self->{SQL} = $archiver;
406             }
407              
408              
409             sub _connect {
410             my ($self) = shift;
411              
412             initlog sprintf ("CONNECTING to %s as %s\n",
413             $self->{DBI_DSN} || $ENV{DBI_DSN},
414             $self->{DBI_USER} || $ENV{DBI_USER} || "(none)");
415              
416             my $dbh = DBI->connect (
417             $self->{DBI_DSN},
418             $self->{DBI_USER},
419             $self->{DBI_PASS},
420             {
421             RaiseError => 0,
422             PrintError => 0,
423             AutoCommit => 0,
424             }
425             );
426              
427             if (!$dbh) {
428             croak $DBI::errstr;
429             }
430              
431             $dbh->{LongReadLen} = $self->{LONGREADLEN};
432             $dbh->{LongTruncOk} = 1;
433              
434             $self->{DBH} = $dbh;
435             }
436              
437              
438             =item B
439              
440             $dblbn->prepare(@tag_list);
441              
442             Retrieves, prepares and caches a list of SQL queries.
443              
444             =cut
445              
446             sub prepare {
447             my ($self, @tags) = @_;
448              
449             foreach my $tag (@tags) {
450             if (! $self->{SQL}->lookup($tag)) {
451             $self->_prepare($tag);
452             }
453             }
454             }
455              
456              
457             =item B
458              
459             $dblbn->can("label");
460              
461             Returns true if a valid SQL block exists for tag "label". Side effect is
462             that the SQL is prepared for later execution.
463              
464             =cut
465              
466             sub can {
467             my ($self, $tag) = @_;
468              
469             return 1 if $self->{SQL}->lookup($tag);
470              
471             eval { $self->_prepare($tag) };
472             return 1 if $self->{SQL}->lookup($tag);
473              
474             return;
475             }
476              
477              
478              
479              
480             =item B
481              
482             $dblbn->execute("label", $data);
483              
484             $data is assumed to be a hash reference. Inputs for bind variables will
485             be obtained from $data. SELECT results will be written back to $data.
486              
487             The SQL block is obtained from the repository specified above.
488              
489             An array of two values is returned:
490             Total number of rows affected by all SQL statements (including SELECTs)
491             Reference to a list of the individual rowcounts for each statement
492              
493             May abort for various reasons, primarily Oracle errors. Will abort
494             if a SELECT is attempted without a $data target.
495              
496             =cut
497              
498             sub execute {
499             my ($self, $tag, $data) = @_;
500              
501             if (! $self->is_connected) {
502             $self->disconnect; # clean up
503             $self->_connect;
504             }
505              
506             my $prepped = $self->{SQL}->lookup($tag);
507             if (!$prepped) {
508             $prepped = $self->_prepare($tag);
509             }
510              
511             execlog "EXECUTE $tag\n";
512              
513             my @rowcounts = $self->_execute($prepped, $data);
514             my $totalrows = 0;
515             map { $totalrows += $_ } @rowcounts;
516              
517             return $totalrows, \@rowcounts;
518             }
519              
520              
521             sub _prepare {
522             my ($self, $tag) = @_;
523              
524             my $sql = $self->{SQL}->find($tag);
525             croak "Unable to find $tag" unless $sql;
526              
527             execlog "PREPARE $tag\n";
528              
529             my @stmts;
530              
531             # for Oracle, support PL/SQL blocks marked by BEGIN...END;
532             # a PL/SQL statement block may contain nothing else
533             if (($self->{DBH}->{Driver}->{Name} =~ /Oracle/i)
534             && ($sql =~ /^\s*BEGIN/))
535             {
536             # print STDERR "\tOracle PL/SQL block\n" if $self->{TRACE};
537             # treat the entire thing as a single statement.
538             push @stmts, $sql;
539             } else {
540              
541             # a SQL statement is identified as a unit
542             # separated from others by whitespace
543             # containing at least one word at the beginning of a line
544             #
545             # Note that comments are NOT stripped, since the comment syntax
546             # varies between databases. But a bunch of stuff that doesn't
547             # look like a SQL statement will be silently ignored, regardless
548             # of syntax.
549              
550             @stmts =
551             grep { /^\s*\w/ms }
552             grep { !/^\s*$/ }
553             split (/\s*(\n\s*){2,}/, $sql);
554             }
555              
556             my @preps;
557              
558             foreach my $stmt (@stmts) {
559             if ($stmt =~ /^include\s+/io) {
560             my ($include) = $stmt =~ /^include\s+(\S+)/o;
561             push @preps, $include;
562             $self->_prepare($include);
563             } else {
564             if ($self->{DBH}->{Driver}->{Name} =~ /Oracle/io) {
565             $stmt =~ s/--.*//mog; # strip out Oracle comments
566             if ($stmt !~ /^\s*BEGIN/) {
567             $stmt =~ s/\s*;$//mso; # erase trailing semicolon
568             }
569             } else {
570             $stmt =~ s/\s*;$//mso; # erase trailing semicolon
571             $stmt =~ s/\s*$//mso; # erase trailing whitespace
572             }
573              
574             my $statement = new DBIx::Librarian::Statement (
575             $self->{DBH},
576             $stmt,
577             MAXSELECTROWS => $self->{MAXSELECTROWS},
578             NAME => $tag,
579             );
580             $statement->{ALLARRAYS} = $self->{ALLARRAYS};
581             push @preps, $statement;
582             }
583             }
584              
585             $self->{SQL}->cache($tag, \@preps);
586              
587             return \@preps;
588             }
589              
590              
591             sub _execute {
592             my ($self, $prep, $data) = @_;
593              
594             my @rowcounts;
595              
596             my $changes = 0;
597             foreach my $stmt_prep (@{$prep}) {
598             if (!ref($stmt_prep)) {
599             # found an include
600             push @rowcounts, $self->execute($stmt_prep, $data);
601             } else {
602             eval {
603             my $rows = $stmt_prep->execute($data);
604             push @rowcounts, $rows;
605             };
606             if ($@) {
607             if ($self->{AUTOCOMMIT} && $changes) {
608             $self->rollback;
609             }
610             die $@;
611             }
612             $changes++ unless $stmt_prep->{IS_SELECT};
613             }
614             }
615              
616             if ($self->{AUTOCOMMIT} && $changes) {
617             # # there was at least one non-SELECT, so better commit here
618             $self->commit;
619             }
620              
621             return @rowcounts;
622             }
623              
624              
625             =item B
626              
627             Invokes commit() on the database handle. Not needed unless
628             $dblbn->delaycommit() has been called.
629              
630             =cut
631              
632             sub commit {
633             my ($self) = @_;
634              
635             execlog "COMMIT\n";
636              
637             $self->{DBH}->commit;
638             }
639              
640             =item B
641              
642             Invokes rollback() on the database handle. Not needed unless
643             $dblbn->delaycommit() has been called.
644              
645             =cut
646              
647             sub rollback {
648             my ($self) = @_;
649              
650             execlog "ROLLBACK\n";
651              
652             $self->{DBH}->rollback;
653             }
654              
655             =item B
656              
657             Sets the AUTOCOMMIT flag. Once set, explicit commit and rollback
658             are not needed.
659              
660             =cut
661              
662             sub autocommit {
663             my ($self) = @_;
664              
665             $self->{AUTOCOMMIT} = 1;
666             }
667              
668             =item B
669              
670             Clears the AUTOCOMMIT flag. Explicit commit and rollback will be
671             needed to apply changes to the database.
672              
673             =cut
674              
675             sub delaycommit {
676             my ($self) = @_;
677              
678             $self->{AUTOCOMMIT} = 0;
679             }
680              
681             =item B
682              
683             $dblbn->disconnect;
684              
685             Disconnect from the database. Database handle and any active statements
686             are discarded.
687              
688             =cut
689              
690             sub disconnect {
691             my ($self) = @_;
692              
693             initlog sprintf ("DISCONNECT %s\n",
694             $self->{DBI_DSN} || $ENV{DBI_DSN});
695              
696             $self->{DBH}->disconnect if $self->{DBH};
697             undef $self->{DBH};
698             $self->{SQL}->reset;
699             }
700              
701             =item B
702              
703             $dblbn->is_connected;
704              
705             Returns boolean indicator whether the database connection is active. This
706             depends on the $dbh->{Active} flag set by DBI, which is driver-specific.
707              
708             =cut
709              
710             sub is_connected {
711             my ($self) = @_;
712              
713             return 1 if $self->{DBH} && $self->{DBH}->ping;
714             }
715              
716             sub DESTROY {
717             my ($self) = @_;
718              
719             $self->disconnect if $self->is_connected;
720             }
721              
722             1;
723              
724             =head1 LOGGING
725              
726             Declares two log channels using Log::Channel, "init" and "exec".
727             Connect and disconnect events are logged to the init channel,
728             query execution (prepare, execute, commit, rollback) to exec.
729              
730             See also the channels for DBIx::Librarian::Statement logging.
731              
732             =head1 AUTHOR
733              
734             Jason W. May
735              
736             =head1 COPYRIGHT
737              
738             Copyright (C) 2001-2003 Jason W. May. All rights reserved.
739             This module is free software; you can redistribute it and/or
740             modify it under the same terms as Perl itself.
741              
742             =head1 TEST SUITE
743              
744             Under development.
745              
746             =head1 SEE ALSO
747              
748             Class:Phrasebook::SQL
749             Ima::DBI
750             SQL::Catalog
751             DBIx::SearchProfiles
752             DBIx::Abstract
753             DBIx::Recordset
754             Tie::DBI
755              
756             Relevant links stolen from SQL::Catalog documentation:
757             http://perlmonks.org/index.pl?node_id=96268&lastnode_id=96273
758             http://perlmonks.org/index.pl?node=Leashing%20DBI&lastnode_id=96268
759              
760             =cut