File Coverage

blib/lib/DB_File/DB_Database.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             =pod
2            
3             =head1 NAME
4            
5             DB_File::DB_Database - Perl module for reading and writing the DB_File data as a mutifield table
6             with index file supported.
7            
8             =cut
9            
10             # ############
11             package DB_File::DB_Database;
12            
13 2     2   3533 use 5.004;
  2         8  
  2         177  
14 2     2   14 use strict;
  2         3  
  2         72  
15 2     2   1036 use DB_File;
  0            
  0            
16             use Fcntl qw( O_RDWR O_RDONLY LOCK_SH LOCK_EX LOCK_UN);
17             # ##############
18             # General things
19            
20             use vars qw( $VERSION $errstr @ISA );
21             $VERSION = 0.031;
22             # Sets the debug level
23             $DB_File::DB_Database::DEBUG = 0;
24             BEGIN {
25             if ($^O =~ /mswin/i) { $DB_File::DB_Database::LOCKING = 0; }
26             else { $DB_File::DB_Database::LOCKING = 1; }
27             require IO::File if( $DB_File::DB_Database::LOCKING );
28             }
29            
30             # print "true_close\n" if ($DB_File::DB_Database::DEBUG);
31            
32             # ###############################################################################
33             # Build the object in the memory, open the file
34             sub new {
35             __PACKAGE__->NullError();
36             my $class = shift;
37             my $new = bless {}, $class;
38             if (@_ and not $new->open(@_)) { return; }
39             return $new;
40             }
41            
42             # ###############################################################################
43             # Open the specified file.
44             sub open {
45             my ($self) = shift;
46             my %options;
47             if (scalar(@_) % 2) { $options{'name'} = shift; }
48             $self->{'DataBase'}->{'OpenOptions'} = { %options, @_ };
49            
50             my %locoptions;
51             @locoptions{ qw( name readonly ) } = @{$self->{'DataBase'}->{'OpenOptions'}}{ qw( name readonly ) };
52             my $FileName = $locoptions{'name'};
53             for my $ext ('', '.db') {
54             if (-f $FileName.$ext) {
55             $locoptions{'name'} = $FileName.$ext;
56             $self->NullError();
57             return $self->real_open(%locoptions);
58             }
59             }
60             $locoptions{'name'} = $FileName;
61             return $self->real_open(%locoptions); # for nice error message
62             }
63             # ###############################################################################
64             # Close the file (and memo)
65             sub close {
66             my $self = shift;
67             $self->real_close;
68             $self->real_close_index( keys %{$self->{'Index'}} );
69             }
70            
71             # ###############################################################################
72             # Creating new file
73             sub create {
74             __PACKAGE__->NullError();
75             my $class = shift;
76             my %options = @_;
77             if (ref $class) {
78             %options = ( %$class, %options ); $class = ref $class;
79             }
80            
81             $options{'permits'}=0640 unless ( $options{'permits'} );
82             my $key;
83             for $key ( qw( name field_names ) ) {
84             if (not defined $options{$key}) {
85             __PACKAGE__->Error("Create Failed: Tag $key must be specified when creating new table\n");
86             return;
87             }
88             }
89             if (-f $options{'name'}) {
90             __PACKAGE__->Error("Taget File already exists\n");
91             return;
92             }
93             $options{'field_names'} = $class->check_field_names($options{'field_names'});
94             $options{'field_types'} = $class->check_field_types($options{'field_types'}, scalar(@{$options{'field_names'}}));
95            
96             my $tmp = $class->new();
97             $tmp->real_create(%options) or return;
98             $tmp->close();
99            
100             return $class->new($options{'name'});
101             }
102             # ###############################################################################
103             # check_field_names
104             sub check_field_names {
105             my ($self, $fields_name) = ( shift, shift );
106             my @fields_name = ref $fields_name ? @$fields_name : ();
107             my @return_fields_name;
108             my $i = 0;
109             my %fields_name;
110             while ( $i < scalar(@fields_name) ) {
111             $fields_name[$i] = uc $fields_name[$i];
112             # if the same field names appears
113             if (not $fields_name{ $fields_name[$i] } ) {
114             push ( @return_fields_name, $fields_name[$i] );
115             }
116             $fields_name{ $fields_name[$i] } = 1;
117             $i++;
118             }
119             return \@return_fields_name;
120             }
121             # ###############################################################################
122             # check_field_types
123             sub check_field_types {
124             my ($self, $fields_type) = ( shift, shift );
125             my @fields_type = ref $fields_type ? @$fields_type : ();
126             my $num = shift;
127             $num = scalar(@fields_type) if not defined $num;
128             my $i = 0;
129             while ( $i < $num ) {
130             $fields_type[$i] = uc substr($fields_type[$i],0,1);
131             # set default type
132             if ( $fields_type[$i] ne 'C' and $fields_type[$i] ne 'N') {
133             $fields_type[$i] = 'C';
134             }
135             $i++;
136             }
137             return \@fields_type;
138             }
139             # ###############################################################################
140             # check_field_names_hash
141             sub check_field_names_hash {
142             my $self = shift;
143             my @fields_name_hash = @_;
144             my $i = 0;
145             while ( $i < scalar(@fields_name_hash) ) {
146             $fields_name_hash[$i] = uc $fields_name_hash[$i];
147             $i+=2;
148             }
149             return @fields_name_hash;
150             }
151             # ###############################################################################
152             # Drop the table
153             sub drop {
154             my $self = shift;
155             $self->drop_index(keys %{$self->{'Index'}});
156             return $self->real_drop();
157             }
158             # ###############################################################################
159             # List of field names, types, lengths and decimals
160             sub field_names { @{shift->{'DataBase'}->{'data_field_names'}}; }
161             sub field_types { @{shift->{'DataBase'}->{'data_field_types'}}; }
162             sub field_name_to_num { my ($self, $name) = @_; $self->{'DataBase'}->{'data_field_names_hash'}->{uc $name}; }
163             sub rows { shift->{'DataBase'}->{'rows'}; }
164             sub select_hits { shift->{'Select'}->{'Result_Num'}; }
165            
166            
167            
168             # ###############################################################################
169             # Reading the records
170             # Returns fields of the specified record; optionally names of the required
171             # fields. If no names are specified, all fields are returned. Returns
172             # empty list on error.
173             sub get_record {
174             my ($self, $id) = (shift, shift);
175             return unless ( $id = $self->check_for_select($id) );
176             $self->get_record_nf( $id, map { $self->field_name_to_num($_); } @_ );
177             }
178             # ###############################################################################
179             sub get_record_hash {
180             my ($self, $id) = @_;
181             return unless ( $id = $self->check_for_select($id) );
182             my @data = $self->get_record_nf($id) or return;
183             my $hash = {};
184             @{$hash}{ ('__ID', $self->field_names) } = @data;
185             return %$hash if wantarray;
186             $hash;
187             }
188             # ###############################################################################
189             sub get_record_nf {
190             my ($self, $id, @fieldnums) = @_;
191             return unless ( $id = $self->check_for_select($id) );
192             my $data = $self->real_read_record($id) or return;
193             return ($id, @$data) if (not @fieldnums);
194            
195             my @return_data = ($id);
196             foreach ( @fieldnums ) {
197             push (@return_data, @$data[$_] );
198             }
199             return @return_data;
200             }
201             # ###############################################################################
202             # Actually read the data
203             sub real_read_record {
204             my ($self, $id) = (shift, shift);
205             return if (not $self->{'DataBase'}->{'db'}->{$id} );
206             $self->csv_prase( $self->{'DataBase'}->{'db'}->{$id} );
207             }
208             # ###############################################################################
209             sub check_for_select {
210             my ($self, $id) = @_;
211             if ( not defined $id ) {
212             $id = shift ( @{$self->{'Select'}->{'Result'}} );
213             return undef if not defined $id;
214             }
215             $id;
216             }
217            
218            
219            
220            
221            
222             # ###############################################################################
223             # Write record, values of the fields are in the argument list.
224             sub set_record {
225             my ($self, $id, @data) = @_;
226             $self->real_write_record($id, @data);
227             }
228             # ###############################################################################
229             # Write record, fields are specified as hash, unspecified are set to undef/empty
230             sub set_record_hash {
231             my ($self, $id) = (shift,shift);
232             my %data = $self->check_field_names_hash(@_);
233             $self->set_record($id, map { $data{$_} } $self->field_names );
234             }
235             # ###############################################################################
236             # Write record, fields specified as hash, unspecified will be unchanged
237             sub update_record_hash {
238             my ($self, $id) = ( shift, shift );
239             my %olddata = $self->get_record_hash($id);
240             return unless %olddata;
241             $self->set_record_hash($id, %olddata, @_);
242             }
243             # ###############################################################################
244             # Write record, values of the fields are in the argument list.
245             sub append_record {
246             my ($self, @data) = @_;
247             $self->real_write_record(undef, @data);
248             }
249             # ###############################################################################
250             # Write record, fields are specified as hash, unspecified are set to undef/empty
251             sub append_record_hash {
252             my $self = shift;
253             my %data = $self->check_field_names_hash(@_);
254             $self->append_record( map { $data{$_} } $self->field_names );
255             }
256             # ###############################################################################
257             # Actually write the data (@newdata = undef means delete record)
258             sub real_write_record {
259             my ($self, $id) = (shift, shift);
260             $id = $self->{'DataBase'}->{'LastRecord'}+1 if (not defined $id);
261             my @newdata = @_;
262             my $olddata;
263             if ( $self->{'DataBase'}->{'rw'} ) {
264             $olddata = $self->real_read_record($id) if (defined $self->{'DataBase'}->{'db'}->{$id});
265             my ($tagname, $key);
266             my ($oldindex,$newindex);
267             while ( ($tagname,$key) = each (%{$self->{'Index'}}) ) {
268             #print "\nOldIndex: ";
269             $oldindex = $self->get_index_string($tagname, $olddata);
270             #print "\nNewIndex: ";
271             $newindex = $self->get_index_string($tagname, \@newdata);
272             # $DB_BTREE->{'compare'} = $self->get_compare_sub('index' => $tagname);
273             if ( not @newdata or $oldindex ne $newindex ) {
274             $self->real_delete_index_record( $tagname, $oldindex, $id ) if (defined $self->{'DataBase'}->{'db'}->{$id});
275             $self->real_insert_index_record( $tagname, $newindex, $id ) if ( @newdata );
276             }
277             }
278             if ( scalar(@newdata) ) {
279             $self->{'DataBase'}->{'db'}->{$id} = $self->csv_combine(@newdata);
280             $self->{'DataBase'}->{'db'}->{'__Total_Records'} ++;
281             if( int($id) > $self->{'DataBase'}->{'LastRecord'} ) {
282             $self->{'DataBase'}->{'db'}->{'__Last_Record'} = int($id);
283             $self->{'DataBase'}->{'LastRecord'} = int($id);
284             }
285             }else {
286             return if (not defined $self->{'DataBase'}->{'db'}->{$id});
287             delete $self->{'DataBase'}->{'db'}->{$id};
288             $self->{'DataBase'}->{'db'}->{'__Total_Records'} --;
289             }
290             }else {
291             $self->Error("Writing Record Failed: File is opened only for reading.\n");
292             return;
293             }
294             $id;
295             }
296            
297             # ###############################################################################
298             # Delete record
299             sub delete_record {
300             my ($self, @id) = @_;
301             my $id;
302             my $num = 0;
303             foreach $id (@id) {
304             $num++ if ( $self->real_write_record( $id ) );
305             }
306             $num;
307             }
308            
309            
310            
311            
312             sub get_hashref { shift->{'db'} }
313            
314            
315            
316            
317            
318            
319            
320            
321             # ###############################################################################
322             # Open the specified file.
323             sub real_create {
324             print "true_create\n" if ($DB_File::DB_Database::DEBUG);
325             my $self = shift;
326             my %options = @_;
327             if (defined $self->{'DataBase'}->{'db'}) { $self->close(); }
328            
329             my %db;
330             if( tie %db, "DB_File", $options{'name'}, O_CREAT|O_RDWR, $options{'permits'}, $DB_HASH) {
331             $db{'__Version'} = $VERSION;
332             $db{'__Last_Record'} = 0;
333             $db{'__Total_Records'} = 0;
334             $db{'__Field_names'} = $self->csv_combine(@{$options{'field_names'}});
335             $db{'__Field_types'} = $self->csv_combine(@{$options{'field_types'}});
336             }else{
337             $self->Error("Error opening file $options{'name'}: $!\n");
338             return;
339             }
340             1; # success
341             }
342             # ###############################################################################
343             # Drop (unlink) the file
344             sub real_drop {
345             my $self = shift;
346             $self->NullError();
347             if (defined $self->{'DataBase'}->{'FileName'}) {
348             my $FileName = $self->{'DataBase'}->{'FileName'};
349             $self->close() if defined $self->{'DataBase'}->{'db'};
350             if (not unlink $FileName)
351             { $self->Error("Error unlinking file $FileName: $!\n"); return; };
352             }
353             1;
354             }
355            
356             # ###############################################################################
357             # Open the specified file.
358             sub real_open {
359             my $self = shift;
360             my %options = @_;
361             if (defined $self->{'DataBase'}->{'db'}
362             and ( $self->{'DataBase'}->{'FileName'} ne $options{'name'}
363             or $self->{'DataBase'}->{'rw'} eq $options{'readonly'} )) { $self->close(); }
364            
365             my %db;
366             my $fh;
367             my $rw = 0;
368             my $ok = 0;
369             my $lock = 0;
370             if (not $options{'readonly'}) {
371             if( $fh = tie %db, "DB_File", $options{'name'}, O_RDWR, 0640, $DB_HASH) {
372             $rw = 1; $ok = 1;
373             }
374             }else {
375             if( $fh = tie %db, "DB_File", $options{'name'}, O_RDONLY, 0640, $DB_HASH) {
376             $rw = 0; $ok = 1;
377             }
378             }
379             if (not $ok) {
380             $self->Error("Error opening file $options{'name'}: $!\n");
381             return;
382             }
383             @{$self->{'DataBase'}}{ qw( fh db FileName rw ) } = ($fh, \%db, $options{'name'}, $rw);
384             $self->{'DataBase'}->{'lockfh'} = $self->database_lock ( 'FileName' => $self->{'DataBase'}->{'FileName'} ,
385             'rw' => $self->{'DataBase'}->{'rw'} ,
386             'permits' => 0640 );
387             $self->read_head;
388             $self->real_open_index;
389             }
390             # ###############################################################################
391             # Open the specified file.
392             sub real_open_index {
393             print "open_index\n" if ($DB_File::DB_Database::DEBUG);
394             my $self = shift;
395             my ($tag_name,$tag_info);
396            
397             while ( ($tag_name,$tag_info) = each( %{$self->{'Index'}}) ) {
398             my $recreate = 0;
399             if (not -f $tag_info->{'FileName'}) {
400             $self->Error("Warning: Can't find Index file ".$tag_info->{'FileName'}." , ReCreated it.\n");
401             $recreate = 1;
402             $self->real_create_index('tag' => $tag_name ,
403             'FileName' => $tag_info->{'FileName'} ,
404             'key' => $tag_info->{'key'} ,
405             'compare' => $self->get_compare_sub('index' => $tag_name),
406             'permits' => 0640 );
407             }
408             my %db;
409             my $fh;
410             my $rw = 0;
411             my $ok = 0;
412             my $lock = 0;
413             $DB_BTREE->{'flags'} = R_DUP;
414             $DB_BTREE->{'compare'} = $self->get_compare_sub('index' => $tag_name);
415             if ( $self->{'DataBase'}->{'rw'} ) {
416             if( $fh = tie %db, "DB_File", $tag_info->{'FileName'}, O_RDWR, 0640, $DB_BTREE) {
417             $rw = 1; $ok = 1;
418             }
419             }else {
420             if( $fh = tie %db, "DB_File", $tag_info->{'FileName'}, O_RDONLY, 0640, $DB_BTREE) {
421             $rw = 0; $ok = 1;
422             }
423             }
424             if (not $ok) {
425             $self->Error("Error opening Index file ".$tag_info->{'FileName'}.": $!\n");
426             return;
427             }
428             @{$tag_info}{ qw( fh db rw ) } = ($fh, \%db, $rw);
429             $tag_info->{'lockfh'} = $self->database_lock ( 'FileName' => $tag_info->{'FileName'} ,
430             'rw' => $self->{'DataBase'}->{'rw'} ,
431             'permits' => 0640 );
432             $self->recreate_index( $tag_name ) if $recreate;
433             }
434             1;
435             }
436             # ###############################################################################
437             # Open the specified file.
438             sub read_head {
439             my $self = shift;
440             if (not defined $self->{'DataBase'}->{'db'}) { $self->close();return; }
441            
442             my $db = $self->{'DataBase'}->{'db'};
443             my ( $data_version, $rows, $data_structure_raw, $data_fieldtype_raw, $index_raw, $index_keyfield_raw)
444             = ($db->{'__Version'}, $db->{'__Total_Records'}, $db->{'__Field_names'}, $db->{'__Field_types'}, $db->{'__Index'}, $db->{'__IndexKeyField'});
445             if (not ($data_version and $data_structure_raw) ) {
446             $self->close();
447             $self->Error("DATA Version Error: This file is not normally created by DB_File::DB_Database.\n"); return;
448             return;
449             }
450             my $data_structure = $self->csv_prase($data_structure_raw);
451             my $data_fieldtype = $self->csv_prase($data_fieldtype_raw);
452             # set fields no to hash
453             my %data_structure_hash;
454             foreach (0 .. scalar(@$data_structure)-1) {
455             $data_structure_hash{ @$data_structure[$_] } = $_;
456             }
457             @{$self->{'DataBase'}}{ qw( data_version rows data_field_names data_field_types data_field_names_hash LastRecord ) }
458             = ($data_version, $rows, $data_structure, $data_fieldtype, \%data_structure_hash, $db->{'__Last_Record'} );
459             # set index tags
460             my $index_tag = $self->csv_prase($index_raw);
461             my $index_keyfield = $self->csv_prase($index_keyfield_raw);
462             foreach (0 .. scalar(@$index_tag)-1) {
463             $self->{'Index'}->{ @$index_tag[$_] } = { 'FileName' => $self->{'DataBase'}->{'FileName'}.'_'.@$index_tag[$_] ,
464             'KeyField' => @$index_keyfield[$_] ,
465             'KeyField_type'=> ($self->field_types)[ $self->field_name_to_num(@$index_keyfield[$_]) ] };
466             }
467             1;
468             }
469             # ###############################################################################
470             # Close the file
471             sub real_close {
472             print "real_close\n" if ($DB_File::DB_Database::DEBUG);
473             my $self = shift;
474             $self->database_unlock( 'lockfh' => $self->{'DataBase'}->{'lockfh'} );
475             undef $self->{'DataBase'}->{'fh'};
476             untie %{$self->{'DataBase'}->{'db'}};
477             delete $self->{'DataBase'};
478             }
479            
480             # ###############################################################################
481             sub errstr {
482             my $self = shift;
483             return ( ref $self ? $self->{'errstr'} : $DB_File::DB_Database::errstr );
484             }
485             # ###############################################################################
486             # Set errstr if there is debug level
487             sub Error {
488             my $self = shift;
489             ( ref $self ? $self->{'errstr'} : $DB_File::DB_Database::errstr ) .= join '', @_;
490             # print @_ if ($DB_File::DB_Database::DEBUG);
491             }
492             # ###############################################################################
493             # Null the errstr
494             sub NullError
495             { shift->Error(''); }
496            
497            
498            
499             # ###############################################################################
500             # Dump
501             sub dump_all {
502             my $self = shift;
503             use Data::Dumper; $Data::Dumper::Indent=1;
504             print &Data::Dumper::Dumper($self);
505             1; # return true since everything went fine
506             }
507             # ###############################################################################
508             # Dump
509             sub dump_data {
510             my $self = shift;
511            
512             my $i = 0;
513             my @field_names = $self->field_names;
514             my @field_types = $self->field_types;
515             print "\n";
516             print "Data File Name: ".$self->{'DataBase'}->{'FileName'}." \n";
517             print " DataVersion : ".$self->{'DataBase'}->{'data_version'}."\n";
518             print " Privility : Read ".($self->{'DataBase'}->{'rw'} ? "and Write" : "Only")."\n";
519             print " Locking : ".($self->{'DataBase'}->{'lockfh'} ? "" : "Not ")."Locked\n";
520             print " ID -> "."@field_names "."\n";
521             print " "."@field_types "."\n";
522             print " Table Data :\n";
523             my ($key, $content_raw, $content, $status);
524             while ( ($key, $content_raw) = each( %{$self->{'DataBase'}->{'db'}} ) ) {
525             if (not $key =~ /^__/ ) {
526             $content = $self->csv_prase($content_raw);
527             print " $key -> "."@$content "."\n";
528             $i++;
529             }
530             }
531             print " Totally : $i Recrods\n";
532            
533             my $x;
534             foreach $_ ( keys %{$self->{'Index'}} ) {
535             print "\n";
536             print "Index $_\n";
537             print " Index File Name: ".$self->{'Index'}->{$_}->{'FileName'}."\n";
538             print " Privility : Read ".($self->{'Index'}->{$_}->{'rw'} ? "and Write" : "Only")."\n";
539             print " Locking : ".($self->{'Index'}->{$_}->{'lockfh'} ? "" : "Not ")."Locked\n";
540             print " KeyField : ".$self->{'Index'}->{$_}->{'KeyField'}."\n";
541             print " Index Content :\n";
542             $i = 0;
543             $x = $self->{'Index'}->{ $_ }->{'fh'};
544             $key = $content = 0;
545             for ($status = $x->seq($key, $content, R_FIRST) ; $status == 0 ; $status = $x->seq($key, $content, R_NEXT) ) {
546             print " $key -> $content\n";
547             $i++;
548             }
549             print " Totally : $i Recrods\n";
550             }
551             1; # return true since everything went fine
552             }
553             # ###############################################################################
554             # Dump
555             sub dump {
556             my $self = shift;
557             use Data::Dumper; $Data::Dumper::Indent=1;
558             print &Data::Dumper::Dumper(shift);
559             1; # return true since everything went fine
560             }
561             # ###############################################################################
562             # CSV string to columns
563             sub csv_prase {
564             my $self = shift;
565             my $string = shift;
566             my $result = [];
567             return $result unless ( $string );
568             $string=','.$string.',';
569             @$result =($string=~ /,("(?:[^"]|(?:[^"]*?""))*?"|[^"]*?)(?=,)/mg);
570             foreach(0..scalar(@$result)-1) {
571             $result->[$_]=~ s/\A"|"\Z//g;
572             $result->[$_]=~ s/""/"/g;
573             }
574             return $result;
575             }
576             # ###############################################################################
577             # columns to CSV string
578             sub csv_combine {
579             my $self = shift;
580             my @content = @_;
581             foreach (0..scalar(@content)-1) {
582             $content[$_]=~ s/"/""/g;
583             $content[$_]="\"$content[$_]\"" if($content[$_]);
584             }
585             return join(',',@content);
586             }
587             # ###############################################################################
588             # Lcok
589             sub database_lock {
590             my $self = shift;
591             my %options = @_;
592             my $lock = 0;
593             my $lockfile = $options{'FileName'}.'.lock';
594             if ( $DB_File::DB_Database::LOCKING ) {
595             my $fh = new IO::File;
596             if ( not $fh->open($lockfile, O_CREAT|O_RDWR, $options{'permits'}) ) {
597             $self->Error("Error occur when making lock file $lockfile: $!.\n");
598             return;
599             }
600             if ( $options{'rw'} ) {
601             if ( $self->_lockex($fh) ) {
602             print "lockex_success\n" if ($DB_File::DB_Database::DEBUG);
603             $lock = 1;
604             }else {
605             $self->Error("Error occur when locking (for read & write) the lock file: $!.\n");
606             return;
607             }
608             }else {
609             if ( $self->_locksh($fh) ) {
610             print "locksh_success\n" if ($DB_File::DB_Database::DEBUG);
611             $lock = 1;
612             }else {
613             $self->Error("Error occur when locking (for read) the lock file: $!.\n");
614             return;
615             }
616             }
617             return $fh;
618             }
619             return;
620             }
621             # ###############################################################################
622             # Unlcok
623             sub database_unlock {
624             my $self = shift;
625             my %options = @_;
626             my $lockfh = $options{'lockfh'};
627             if ( $lockfh ) {
628             if ( $self->_unlock($lockfh) ) {
629             print "unlock_success\n" if ($DB_File::DB_Database::DEBUG);
630             $lockfh->close;
631             }else {
632             $self->Error("Error occur when unlocking the lock file: $!.\n");
633             return;
634             }
635             }
636             1;
637             }
638             #
639             #sub _locksh { flock(shift, LOCK_SH); }
640             #sub _lockex { flock(shift, LOCK_EX); }
641             #sub _unlock { flock(shift, LOCK_UN); }
642            
643             sub _locksh { 1; }
644             sub _lockex { 1; }
645             sub _unlock { 1; }
646            
647            
648            
649            
650            
651             # ###############################################################################
652             # Compare sub maker
653             sub get_compare_sub {
654             my $self = shift;
655             my %options = @_;
656            
657             my $compare_sub;
658             if ( defined $options{'index'} ) {
659             $options{'key'} = $self->{'Index'}->{ $options{'index'} }->{'KeyField'};
660             }
661             if( defined $options{'type'} ) {
662             if ( $options{'type'} eq 'N' ) {
663             $compare_sub = sub {
664             $_[0] <=> $_[1];
665             }
666             }else {
667             $compare_sub = sub {
668             $_[0] cmp $_[1];
669             }
670             }
671             }elsif( defined $options{'key'} ) { # eg. key => 'ID(10)+-Age(2)'
672             my @key = split(/\+/,$options{'key'});
673             my ($key, $type, $length, $reverse);
674             my $position = 0;
675             my $code = "\$compare_sub = sub {\n";
676             foreach $key (@key) {
677             ($key, $length) = split(/\(/,$key);
678             $reverse = ($key =~ s/^-//g);
679             ($length) = split(/\)/,$length);
680             if (defined $length) { $length = ",$length"; }
681             $type = $self->{'DataBase'}->{'data_field_types'}->[ $self->field_name_to_num($key) ];
682             # print ($key, $type, $length, $reverse);
683             if ($reverse) { $code .= "substr(\$_[1],$position$length)"; }
684             else { $code .= "substr(\$_[0],$position$length)"; }
685             if ($type eq 'N' ) { $code .= ' <=> '; }
686             else { $code .= ' cmp '; }
687             if ($reverse) { $code .= "substr(\$_[0],$position$length)"; }
688             else { $code .= "substr(\$_[1],$position$length)"; }
689             $code .= "\n or \n";
690             $position += $length;
691             }
692             $code .= " 0\;\n}\;";
693             eval($code);
694             }
695             $compare_sub;
696             }
697            
698             # ###############################################################################
699             # make index keywords
700             sub get_index_string {
701             my $self = shift;
702             my ($tag_name, $dataref) = @_;
703             return if(not defined $self->{'Index'}->{ $tag_name } );
704            
705             my @key = split(/\+/,$self->{'Index'}->{ $tag_name }->{'KeyField'});
706             my ($key, $length, $reverse);
707             my $result;
708             foreach $key (@key) {
709             ($key, $length) = split(/\(/,$key);
710             $reverse = ($key =~ s/^-//g);
711             ($length) = split(/\)/,$length);
712             $length = int $length;
713             if ($length) {
714             $result .= sprintf("%${length}s", $dataref->[ $self->field_name_to_num($key) ]);
715             }else {
716             $result .= $dataref->[ $self->field_name_to_num($key) ];
717             }
718             }
719             return $result;
720             }
721            
722             # ###############################################################################
723             # Creating new index
724             sub create_index {
725             my $self = shift;
726             my %options = @_;
727             return unless (defined $self->{'DataBase'}->{'db'} and $self->{'DataBase'}->{'rw'});
728             return unless (defined $options{'name'} and defined $options{'key'});
729            
730             my $tag_name = uc $options{'name'};
731             $options{'permits'} = 0640 unless ( $options{'permits'} );
732            
733             my %createoptions = ( 'tag' => $tag_name ,
734             'FileName' => $self->{'DataBase'}->{'FileName'}.'_'.$tag_name ,
735             'key' => uc $options{'key'} ,
736             'compare' => $self->get_compare_sub('key' => uc $options{'key'}), # 'type' => $self->{'DataBase'}->{'data_field_types'}->[ $self->field_name_to_num($options{'key'}) ]
737             'permits' => $options{'permits'} );
738             if (-f $createoptions{'FileName'} or defined $self->{'Index'}->{$tag_name}) {
739             $self->Error("Taget Index File '$createoptions{'FileName'}' already exists.\n");
740             return;
741             }
742             $self->real_create_index(%createoptions) or return;
743            
744             my @index_tags;
745             my @index_keyfields;
746             my ($other_tag_name,$key);
747             while ( ($other_tag_name,$key) = each (%{$self->{'Index'}}) ) {
748             push (@index_tags, $other_tag_name);
749             push (@index_keyfields, $key->{'KeyField'});
750             }
751             push (@index_tags, $tag_name);
752             push (@index_keyfields, $createoptions{'key'});
753             $self->{'DataBase'}->{'db'}->{'__Index'} = $self->csv_combine(@index_tags);
754             $self->{'DataBase'}->{'db'}->{'__IndexKeyField'} = $self->csv_combine(@index_keyfields);
755            
756             $self->close();
757             $self->open( %{$self->{'DataBase'}->{'OpenOptions'}} );
758             $self->recreate_index( $tag_name );
759             }
760             # ###############################################################################
761             # Open the specified file.
762             sub real_create_index {
763             print "true_create_index\n" if ($DB_File::DB_Database::DEBUG);
764             my $self = shift;
765             my %options = @_;
766            
767             my %db;
768             $DB_BTREE->{'flags'} = R_DUP;
769             $DB_BTREE->{'compare'} = $options{'compare'};
770             if( tie %db, "DB_File", $options{'FileName'}, O_CREAT, $options{'permits'}, $DB_BTREE ) {
771             untie %db;
772             }else{
773             $self->Error("Error creating index file $options{'FileName'}: $!\n");
774             return;
775             }
776             1; # success
777             }
778             # ###############################################################################
779             # Close the file
780             sub real_close_index {
781             my $self = shift;
782             my @tag_names = @_;
783             my $tag_name;
784             foreach $tag_name (@tag_names) {
785             $self->database_unlock( 'lockfh' => $self->{'Index'}->{$tag_name}->{'lockfh'} );
786             untie %{$self->{'Index'}->{$tag_name}->{'db'}};
787             }
788             delete $self->{'Index'};
789             }
790            
791             # ###############################################################################
792             # Drop the table
793             sub drop_index {
794             my $self = shift;
795             my @tag_names = map(uc $_, @_);
796             my $tag_name;
797            
798             foreach $tag_name (@tag_names) {
799             next if( not defined $self->{'Index'}->{$tag_name} );
800             $self->real_drop_index( $tag_name );
801             delete $self->{'Index'}->{$tag_name};
802             delete $self->{'DataBase'}->{'db'}->{'__Index_'.$tag_name};
803             }
804             my @index_tags;
805             my @index_keyfields;
806             my ($other_tag_name,$key);
807             while ( ($other_tag_name,$key) = each (%{$self->{'Index'}}) ) {
808             push (@index_tags, $other_tag_name);
809             push (@index_keyfields, $key->{'KeyField'});
810             }
811             $self->{'DataBase'}->{'db'}->{'__Index'} = $self->csv_combine(@index_tags);
812             $self->{'DataBase'}->{'db'}->{'__IndexKeyField'} = $self->csv_combine(@index_keyfields);
813             }
814             # ###############################################################################
815             # Drop (unlink) the file
816             sub real_drop_index {
817             my $self = shift;
818             my $tag_name = shift;
819             my $FileName = $self->{'Index'}->{$tag_name}->{'FileName'};
820            
821             undef $self->{'Index'}->{$tag_name};
822            
823             if (not unlink $FileName)
824             { $self->Error("Error unlinking Index file $FileName: $!\n"); return; };
825             1;
826             }
827             # ###############################################################################
828             # Recreate Index file.
829             sub recreate_index {
830             print "recreate_index\n" if ($DB_File::DB_Database::DEBUG);
831             my $self = shift;
832             my @tag_names = map(uc $_, @_);
833             my $tag_name;
834             foreach $tag_name (@tag_names) {
835             if( not defined $self->{'Index'}->{$tag_name} ) {
836             $self->Error("Index Tag name $tag_name not found.\n");
837             next;
838             }
839             # it has sth wrong: after recreate, should close then open again
840             # and i don't know why
841             undef %{ $self->{'Index'}->{$tag_name}->{'db'} };
842             # my @ids = keys %{$self->{'Index'}->{$tag_name}->{'db'}};
843             # foreach (0..@ids-1 ) {
844             # $self->{'Index'}->{$tag_name}->{'fh'}->del($_);
845             # delete $self->{'Index'}->{$tag_name}->{'db'}->{@ids[$_]};
846             # }
847             my ($key, $content_raw);
848             my $content;
849             my $indexdata;
850             while ( ($key, $content_raw) = each( %{$self->{'DataBase'}->{'db'}} ) ) {
851             if (not $key =~ /^__/ ) {
852             $content = $self->csv_prase($content_raw);
853             $indexdata = $self->get_index_string($tag_name, $content);
854             $self->real_insert_index_record( $tag_name, $indexdata, $key );
855             }
856             }
857             $self->dump_all;
858             }
859             1; # success
860             }
861             # ###############################################################################
862             # real_delete_index_record
863             sub real_delete_index_record {
864             print "real_delete_index_record\n" if ($DB_File::DB_Database::DEBUG);
865             my $self = shift;
866             my ( $tag_name, $content, $id ) = @_;
867            
868             $self->{'Index'}->{$tag_name}->{'fh'}->del_dup($content, $id);
869             }
870             # ###############################################################################
871             # real_insert_index_record
872             sub real_insert_index_record {
873             print "real_insert_index_record\n" if ($DB_File::DB_Database::DEBUG);
874             my $self = shift;
875             my ( $tag_name, $content, $id ) = @_;
876             print "Index insert : $id -> $content\n" if ($DB_File::DB_Database::DEBUG);
877             # $DB_BTREE->{'compare'} = $self->get_compare_sub('index' => $tag_name);
878             $self->{'Index'}->{$tag_name}->{'db'}->{$content} = $id;
879             }
880            
881            
882             # ###############################################################################
883             # Select the records
884             # Returns fields of the specified record; optionally names of the required
885             # fields. If no names are specified, all fields are returned. Returns
886             # empty list on error.
887             sub prepare_select {
888             my $self = shift;
889             my %options = @_;
890             if (not defined $self->{'DataBase'}->{'db'} ) {
891             $self->Error("Data File Not Opened. $!\n");
892             return;
893             }
894             $self->{'Select'}->{'Result'} = [];
895             $self->{'Select'}->{'Result_Num'} = 0;
896             my %search = $self->check_field_names_hash(%{$options{'where'}});
897             my @search = map { $search{$_} } $self->field_names;
898             my @cut;
899             if (defined $options{'top'}) {
900             @cut = (0, 0, $options{'top'});
901             }elsif (defined $options{'cut'}) {
902             @cut = (0, @{$options{'cut'}} );
903             }else {
904             @cut = (0, 0, -1);
905             }
906            
907             my $id;
908             my @content;
909             my $i;
910             my $ok;
911             if (defined $options{'seek'} and defined $options{'seek'}->{'index'}) {
912             my $tag = uc $options{'seek'}->{'index'};
913             if (not defined $self->{'Index'}->{ $tag }) {
914             $self->Error("Index '$tag' Not Exists. \n");
915             return;
916             }
917            
918             my ($status, $started);
919             my ($from, $to) = ($options{'seek'}->{'from'}, $options{'seek'}->{'to'});
920             my $x = $self->{'Index'}->{ $tag }->{'fh'};
921             my $compare_sub = $self->get_compare_sub('index' => $tag);
922             $id = undef;
923             if (defined $from) { # ?am i right?
924             $status = $x->seq($from, $id, R_CURSOR);
925             $started = 1;
926             }else {
927             $status = 0;
928             $started = 0;
929             }
930             while ($status == 0) {
931             last if ( defined $options{'seek'}->{'to'} and &$compare_sub($from, $to) == 1 );
932             if ($started) {
933             $ok = 1;
934             if (defined $options{'where'}) {
935             (undef, @content) = $self->get_record($id);
936             foreach $i ( 0..scalar(@{$self->{'DataBase'}->{'data_field_names'}})-1 ) {
937             next if not defined $search[$i];
938             if ( $content[$i] !~ /$search[$i]/ ) {
939             $ok = 0;
940             last;
941             }
942             }
943             }
944             if ( $ok ) {
945             $cut[0]++;
946             last if ( $cut[2] > 0 and $cut[0] > $cut[2] );
947             if ( $cut[0] >= $cut[1] ) {
948             push ( @{$self->{'Select'}->{'Result'}} , $id );
949             $self->{'Select'}->{'Result_Num'}++;
950             }
951             }
952             }else {
953             $started = 1;
954             }
955             $id = undef;
956             $status = $x->seq($from, $id, R_NEXT);
957             }
958             }else { # no index specified
959             foreach $id ( keys %{$self->{'DataBase'}->{'db'}} ) {
960             if (not $id =~ /^__/ ) {
961             $ok = 1;
962             if (defined $options{'where'}) {
963             (undef, @content) = $self->get_record($id);
964             foreach $i ( 0..scalar(@{$self->{'DataBase'}->{'data_field_names'}})-1 ) {
965             next if not defined $search[$i];
966             if ( $content[$i] !~ /$search[$i]/ ) {
967             $ok = 0;
968             last;
969             }
970             }
971             }
972             if ( $ok ) {
973             $cut[0]++;
974             last if ( $cut[2] > 0 and $cut[0] > $cut[2] );
975             if ( $cut[0] >= $cut[1] ) {
976             push ( @{$self->{'Select'}->{'Result'}} , $id );
977             $self->{'Select'}->{'Result_Num'}++;
978             }
979             }
980             }
981             }
982             }
983             1;
984             }
985            
986            
987             1;
988             __END__