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