File Coverage

blib/lib/IMDB/Local/DB.pm
Criterion Covered Total %
statement 18 119 15.1
branch 0 42 0.0
condition 0 45 0.0
subroutine 6 15 40.0
pod 2 9 22.2
total 26 230 11.3


line stmt bran cond sub pod time code
1             package IMDB::Local::DB;
2              
3 1     1   17 use 5.006;
  1         2  
  1         25  
4 1     1   4 use strict;
  1         1  
  1         22  
5 1     1   3 use warnings;
  1         1  
  1         37  
6              
7             =head1 NAME
8              
9             IMDB::Local::DB - Direct access to IMDB::Local database.
10              
11             =head1 VERSION
12              
13             Version 0.01
14              
15             =cut
16              
17             our $VERSION = '1.00';
18              
19             =head1 SYNOPSIS
20              
21             Package to interact with the IMDB:Local Database.
22              
23             use IMDB::Local::DB;
24              
25             my $DB=new IMDB::Local::DB(database=>"/home/user/imdbLocal-data/imdb.db");
26             ...
27              
28             =head1 SUBROUTINES/METHODS
29              
30             =head2 new
31              
32             This package extends Class::MethodMaker (making use of the -hash new option)
33              
34             db_AutoCommit - scalar - default is 0, if set database
35             driver - scalar - default "SQLite" (currently only support dbi driver)
36             database - scalar - default "imdb.db" - relative path to IMDB::Local database
37              
38             Modifying any options after new() is not supported and unexpected results may occur.
39              
40             =cut
41              
42 1     1   3 use base qw(IMDB::Local::DB::Base);
  1         1  
  1         337  
43              
44              
45             use Class::MethodMaker
46             [
47 1         12 scalar => [{-default => 0}, 'db_AutoCommit'],
48             scalar => [{-default => 'SQLite'}, 'driver'],
49             scalar => [{-default => 'imdb.db'}, 'database'],
50             scalar => [{-default => ''}, 'passwd'],
51             scalar => [{-default => ''}, 'user'],
52             new => [qw/ -init -hash new /] ,
53 1     1   6 ];
  1         1  
54              
55              
56             sub init($)
57             {
58 0     0 0   my ($self)=@_;
59             }
60              
61             =head2 delete
62              
63             Delete the database. This must be called after new() and prior to connect().
64              
65             =cut
66              
67             sub delete($)
68             {
69 0     0 1   my ($self)=@_;
70 0           unlink($self->database());
71             }
72              
73             =head2 connect
74              
75             Connect and auto-create the database.
76              
77             =cut
78              
79             sub connect($)
80             {
81 0     0 1   my ($self)=@_;
82              
83 0           my $initIt=0;
84 0 0         if ( ! -f $self->database ) {
85 0           $initIt=1;
86             }
87              
88 0           my $c=$self->SUPER::connect();
89 0 0         if ( $c ) {
90             #$c->dbh->{AutoCommit}=0;
91             }
92 0 0 0       if ( $c && $initIt==1 ) {
93 0 0         if ( $self->driver eq 'SQLite' ) {
94 0           my $dbh=$self->dbh();
95 0           $dbh->do("PRAGMA page_size = 8192");
96             }
97 0           $self->schema_init();
98             }
99 0           return($c);
100             }
101              
102             sub schema_init($)
103             {
104 0     0 0   my ($self)=@_;
105              
106 0           for ($self->table_list) {
107 0           print "dropping table: $_\n";
108 0           $self->runSQL("drop table $_");
109             }
110              
111 0           local $/ = undef;
112 0           my $sql = ;
113 0           my @statements = split(/;\s*\n/, $sql);
114              
115 0           foreach my $stmt (@statements) {
116 0 0         next if ($stmt =~ /^\s*$/o);
117              
118 0 0         if ( !$self->runSQL($stmt) ) {
119 0           print "Error executing:\n$stmt\n\n";
120              
121 0           print $self->dbh->errstr()."\n";
122 0           return(0);
123             }
124             }
125 0           $self->insert_row('Versions', undef, (schema_version=>1));
126 0           $self->commit();
127 0           return(1);
128             }
129             sub create_table_indexes($$)
130             {
131 0     0 0   my ($self, $table)=@_;
132              
133 0 0 0       if ( !defined($table) || $table eq 'Titles' ) {
134 0           $self->runSQL("CREATE INDEX Titles_Idx1 on Titles (Year)");
135 0           $self->runSQL("CREATE INDEX Titles_Idx2 on Titles (SearchTitle)");
136 0           $self->runSQL("CREATE INDEX Titles_Idx3 on Titles (ParentID)");
137             }
138 0 0 0       if ( !defined($table) || $table eq 'Directors') {
139 0           $self->runSQL("CREATE INDEX Directors_Idx1 on Directors (SearchName)");
140 0           $self->runSQL("CREATE INDEX Directors_Idx2 on Titles2Directors (TitleID)");
141             }
142 0 0 0       if ( !defined($table) || $table eq 'Actors') {
143 0           $self->runSQL("CREATE INDEX Actors_Idx1 on Actors (SearchName)");
144 0           $self->runSQL("CREATE INDEX Actors_Idx2 on Actors (Name)");
145 0           $self->runSQL("CREATE INDEX Actors_Idx3 on Titles2Actors (TitleID)");
146 0           $self->runSQL("CREATE INDEX Actors_Idx4 on Titles2Hosts (TitleID)");
147 0           $self->runSQL("CREATE INDEX Actors_Idx5 on Titles2Narrators (TitleID)");
148             }
149 0 0 0       if ( !defined($table) || $table eq 'Genres') {
150 0           $self->runSQL("CREATE INDEX Genres_Idx1 on Titles2Genres (TitleID)");
151             }
152 0 0 0       if ( !defined($table) || $table eq 'Ratings') {
153 0           $self->runSQL("CREATE INDEX Ratings_Idx1 on Ratings (TitleID)");
154             }
155 0 0 0       if ( !defined($table) || $table eq 'Keywords') {
156 0           $self->runSQL("CREATE INDEX Keywords_Idx1 on Titles2Keywords (TitleID)");
157             }
158 0 0 0       if ( !defined($table) || $table eq 'Plots' ) {
159 0           $self->runSQL("CREATE INDEX Plots_Idx1 on Plots (TitleID)");
160             }
161 0           return(1);
162             }
163              
164             sub drop_table_indexes($$)
165             {
166 0     0 0   my ($self, $table)=@_;
167              
168 0 0 0       if ( !defined($table) || $table eq 'Titles' ) {
169 0           $self->runSQL("DROP INDEX IF EXISTS Titles_Idx1");
170 0           $self->runSQL("DROP INDEX IF EXISTS Titles_Idx2");
171 0           $self->runSQL("DROP INDEX IF EXISTS Titles_Idx3");
172             }
173 0 0 0       if ( !defined($table) || $table eq 'Directors') {
174 0           $self->runSQL("DROP INDEX IF EXISTS Directors_Idx1");
175 0           $self->runSQL("DROP INDEX IF EXISTS Directors_Idx2");
176             }
177 0 0 0       if ( !defined($table) || $table eq 'Actors') {
178 0           $self->runSQL("DROP INDEX IF EXISTS Actors_Idx1");
179 0           $self->runSQL("DROP INDEX IF EXISTS Actors_Idx2");
180 0           $self->runSQL("DROP INDEX IF EXISTS Actors_Idx3");
181 0           $self->runSQL("DROP INDEX IF EXISTS Actors_Idx4");
182 0           $self->runSQL("DROP INDEX IF EXISTS Actors_Idx5");
183             }
184 0 0 0       if ( !defined($table) || $table eq 'Genres') {
185 0           $self->runSQL("DROP INDEX IF EXISTS Genres_Idx1");
186             }
187 0 0 0       if ( !defined($table) || $table eq 'Ratings') {
188 0           $self->runSQL("DROP INDEX IF EXISTS Ratings_Idx1");
189             }
190 0 0 0       if ( !defined($table) || $table eq 'Keywords') {
191 0           $self->runSQL("DROP INDEX IF EXISTS Keywords_Idx1");
192             }
193 0 0 0       if ( !defined($table) || $table eq 'Plots' ) {
194 0           $self->runSQL("DROP INDEX IF EXISTS Plots_Idx1");
195             }
196 0           return(1);
197             }
198              
199             # Convert a title into a searchtitle by lowercasing,
200             # making it ASCII and removing punctuation.
201             #
202             sub makeSearchableTitle($;$;$) {
203 0     0 0   my ($self, $str) = @_;
204 0           return lc $self->RemovePunctuation(lc( CharsetMap( $str ) ));
205             }
206              
207              
208 1     1   2667 use Text::Unidecode;
  1         1682  
  1         261  
209              
210             # All characters outside the ASCII range (0x00-0x7F) are replaced by ASCII equivalents,
211             # using function Text::Unidecode::unidecode
212             #
213             sub CharsetMap($) {
214 0     0 0   my ($str) = @_;
215            
216             # do replacements that unidecode doesn't know about (or does wrong)
217             ### IT WOULD BE NICE IF THESE WERE IN A TABLE
218 0           $str =~ s/\x{0133}/ij/og; # 'ij' -> ij ("" in unidecode)
219 0           $str =~ s/\x{20ac}/EUR/og; # euro symbol -> EUR (EU in unidecode)
220 0           $str =~ s/\x{2122}/TM/og; # trademark symbol -> TM ("" in unidecode)
221 0           $str =~ s/\x{a3}/GBP/og; # pound sign -> GBP (PS in unidecode)
222            
223             # now do the real decode
224 0           $str = unidecode($str);
225            
226             #print "[$str]\n" if ($debug);
227            
228 0           return ($str);
229             }
230              
231              
232             my @punctuation;
233              
234             # Function that removes all punctuation and whitespace from a string.
235             # '&' is converted to 'and' along the way
236             sub RemovePunctuation($;$;$)
237             {
238 0     0 0   my ($self, $str) = @_;
239              
240             # Load the array of hashes that contain the punctuation
241             # replacements in priority order
242 0 0         if ( !@punctuation ) {
243 0           my @plist = @{$self->select2Matrix("select priority,pattern,replacement from Punctuation order by priority")};
  0            
244 0           my $cnt = 0;
245 0           foreach my $p (@plist) {
246 0           my $pattern = $p->[1];
247 0           my $compiled = qr/$pattern/i;
248 0           $punctuation[$cnt]{origpattern} = $pattern;
249 0           $punctuation[$cnt]{pattern} = $compiled;
250 0           $punctuation[$cnt]{replacement} = $p->[2];
251 0           $cnt++;
252             }
253             }
254              
255 0           foreach my $ref (@punctuation) {
256             #print "[$str] $ref->{origpattern} " if ($debug);
257 0           $str =~ s/$ref->{pattern}/$ref->{replacement}/g;
258             #print "[$str]\n" if ($debug);
259             }
260             #print "<$str>\n--------\n" if ($debug);
261 0           return ($str);
262             }
263              
264             =head1 INHERITED FUNCTIONS
265              
266             The following functions are inherited from IMDB::Local::Base.
267              
268             =head2 disconnect
269              
270             Disconnect from the database. Note that for those lazy programmers that fail to call disconnect, the disconnect will be called when the
271             object is destructed through perl's DESTROY.
272              
273             =head2 isConnected
274              
275             Check to see if there has been previous successful 'connect' call.
276              
277             =head2 quote
278              
279             Call quote subroutine on DBI handle. Quote must not be called while not connected.
280              
281             =head2 commit
282              
283             Commit a DBI transaction (should only be used if db_AutoCommit was zero).
284              
285             =head2 last_inserted_key
286              
287             Retrieve the last inserted key for a given table and primaryKey.
288              
289             =head2 runSQL
290              
291             Execute a sql statement and return 1 upon success and 0 upon success. Upon failure, carp() is called with the sql statement.
292              
293             =head2 runSQL_err
294              
295             Return DBI->err() to retrieve error status of previous call to runSQL.
296              
297             =head2 runSQL_errstr
298              
299             Return DBI->errstr() to retrieve error status of previous call to runSQL.
300              
301             =head2 prepare
302              
303             Return DBI->prepare() for a given statement.
304              
305             =head2 execute
306              
307             Wrapper for calling DBI->prepare() and DBI->exeute() fora given query. Upon success the DBI->prepare() handle is returned.
308             Upon failure, warn() is called with the query statement string and undef is returned.
309              
310             =head2 insert_row
311              
312             Execute a table insertion and return the created primaryKey (if specified).
313             If primaryKey is not defined, 1 is returned upon success
314              
315             =head2 query2SQLStatement
316              
317             Construct an sql query using a hash containing:
318              
319             fields - required array of fields to select
320             tables - required array of tables to select from
321             wheres - optional array of where clauses to include (all and'd together)
322             groupbys - optional array of group by clauses to include
323             sortByField - optional field to sort by (if prefixed with -, then sort is reversed)
324             orderbys - optional array of order by clauses to include
325             offset - offset of returned rows
326             limit - optional integer value to limit # of returned rows
327              
328             =head2 findRecords
329              
330             Call query2SQLStatement with the given hash arguments and return a IMDB::Local::DB::RecordIterator handle.
331              
332             In addition to the query2SQLStatement arguments the following are optional:
333             cacheBy - set cacheBy value in returned IMDB::Local::DB::RecordIterator handle. If not specified, limit is used.
334              
335             =head2 rowExists
336              
337             Check to see at least one row exists with value in 'column' in the specified 'table'
338              
339             =head2 select2Scalar
340              
341             Execute the given sql statement and return the value in a single scalar value
342              
343             =head2 select2Int
344              
345             Execute the given sql statement and return the value cast as an integer (ie int(returnvalue))
346              
347             =head2 select2Array
348              
349             Execute the given sql statement and return an array with all the results.
350              
351             =head2 select2Matrix
352              
353             Execute the given sql statement and return an array of arrays, each containing a row of values
354              
355             =head2 select2HashRef
356              
357             Execute the given sql statement and return a reference to a hash with the result
358              
359             =head2 select2Hash
360              
361             Execute the given sql statement and return a reference ot a hash containing the given row.
362              
363             =head2 table_list
364              
365             Retrieve a list of tables available. Created Tables or Views created after connect() may not be included.
366              
367             =head2 table_exists
368              
369             Check to see if a given table exists. Uses table_list.
370              
371             =head2 column_info
372              
373             Retrieve information about a given column in a table. Changes to columns made after connect() may not be included.
374              
375             Returns a list of columns in a database/driver specific order containing:
376             COLUMN_NAME - name of the column
377             TYPE_NAME - data type (if available)
378             COLUMN_SIZE - size of column data (if available)
379             IS_NULL - true/false if column is nullable
380             IS_PRIMARY_KEY - 1 or 0 if column is a primary key
381              
382             =head2 column_list
383              
384             Retrieve a list of column names in column_list order.
385              
386             =head2 writeQuery2CSV
387              
388             Run an sql query and output the result to the specified file in Text::CSV format
389              
390             =head2 appendCSV2Table
391              
392             Parse the given CVS file (which must have column names that match a the given table) and insert each row
393             as a new row into the specified table.
394              
395             Upon success, returns > 0, number of rows successfully inserted.
396             Returns 0 if open() on the given file fails.
397              
398             =head2 table_row_count
399              
400             Retrieve the # of rows in a given table.
401              
402             =head2 table_report
403              
404             Retrieve a reference to an array of arrays, each sub-array containing [table, #ofRows, data-size-in-KBs, index-size-in-KBs]
405              
406             =head1 AUTHOR
407              
408             jerryv, C<< >>
409              
410             =head1 BUGS
411              
412             Please report any bugs or feature requests to C, or through
413             the web interface at L. I will be notified, and then you'll
414             automatically be notified of progress on your bug as I make changes.
415              
416              
417             =head1 SUPPORT
418              
419             You can find documentation for this module with the perldoc command.
420              
421             perldoc IMDB::Local::DB
422              
423              
424             You can also look for information at:
425              
426             =over 4
427              
428             =item * RT: CPAN's request tracker (report bugs here)
429              
430             L
431              
432             =item * AnnoCPAN: Annotated CPAN documentation
433              
434             L
435              
436             =item * CPAN Ratings
437              
438             L
439              
440             =item * Search CPAN
441              
442             L
443              
444             =back
445              
446              
447             =head1 ACKNOWLEDGEMENTS
448              
449              
450             =head1 LICENSE AND COPYRIGHT
451              
452             Copyright 2015 jerryv.
453              
454             This program is free software; you can redistribute it and/or modify it
455             under the terms of the the Artistic License (2.0). You may obtain a
456             copy of the full license at:
457              
458             L
459              
460             Any use, modification, and distribution of the Standard or Modified
461             Versions is governed by this Artistic License. By using, modifying or
462             distributing the Package, you accept this license. Do not use, modify,
463             or distribute the Package, if you do not accept this license.
464              
465             If your Modified Version has been derived from a Modified Version made
466             by someone other than you, you are nevertheless required to ensure that
467             your Modified Version complies with the requirements of this license.
468              
469             This license does not grant you the right to use any trademark, service
470             mark, tradename, or logo of the Copyright Holder.
471              
472             This license includes the non-exclusive, worldwide, free-of-charge
473             patent license to make, have made, use, offer to sell, sell, import and
474             otherwise transfer the Package with respect to any patent claims
475             licensable by the Copyright Holder that are necessarily infringed by the
476             Package. If you institute patent litigation (including a cross-claim or
477             counterclaim) against any party alleging that the Package constitutes
478             direct or contributory patent infringement, then this Artistic License
479             to you shall terminate on the date that such litigation is filed.
480              
481             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
482             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
483             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
484             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
485             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
486             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
487             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
488             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
489              
490              
491             =cut
492              
493             1; # End of IMDB::Local::DB
494              
495             __DATA__