/[wait]/trunk/lib/WAIT/Database.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /trunk/lib/WAIT/Database.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

branches/CPAN/lib/WAIT/Database.pm revision 11 by unknown, Fri Apr 28 15:41:10 2000 UTC trunk/lib/WAIT/Database.pm revision 108 by dpavlin, Tue Jul 13 17:41:12 2004 UTC
# Line 1  Line 1 
1  #                              -*- Mode: Perl -*-  #                              -*- Mode: cperl -*-
2  # Database --  # $Basename: Database.pm $
3  # ITIID           : $ITI$ $Header $__Header$  # $Revision: 1.14 $
4  # Author          : Ulrich Pfeifer  # Author          : Ulrich Pfeifer
5  # Created On      : Thu Aug  8 09:44:13 1996  # Created On      : Thu Aug  8 09:44:13 1996
6  # Last Modified By: Ulrich Pfeifer  # Last Modified By: Ulrich Pfeifer
7  # Last Modified On: Sun Nov 22 18:44:48 1998  # Last Modified On: Sat Apr 15 16:15:29 2000
8  # Language        : CPerl  # Language        : CPerl
 # Update Count    : 249  
 # Status          : Unknown, Use with caution!  
9  #  #
10  # Copyright (c) 1996-1997, Ulrich Pfeifer  # (C) Copyright 1996-2000, Ulrich Pfeifer
11  #  #
12    
13  =head1 NAME  =head1 NAME
# Line 33  use strict; Line 31  use strict;
31  use FileHandle ();  use FileHandle ();
32  use File::Path qw(rmtree);  use File::Path qw(rmtree);
33  use WAIT::Table ();  use WAIT::Table ();
34    use BerkeleyDB;
35  use Fcntl;  use Fcntl;
36  use Carp;  use Carp; # will use autouse later
37  my ($HAVE_DATA_DUMPER, $HAVE_STORABLE);  use Storable qw(nfreeze thaw);
38    use vars qw($VERSION);
39    use Data::Dumper;
40    
41  BEGIN {  $VERSION = "2.000";
42    eval { require Data::Dumper };  
43    $HAVE_DATA_DUMPER = 1 if $@ eq '';  #$WAIT::Database::Pagesize = 1*1024;
44    eval { require Storable };  #$WAIT::Database::Cachesize = 4*1024*1024;
45    $HAVE_STORABLE    = 1 if $@ eq '';  
46    $HAVE_DATA_DUMPER || $HAVE_STORABLE ||  # use autouse Carp => qw( croak($) );
     die "Could not find Data::Dumper nor Storable";  
   $Storable::forgive_me = 1;  
 }  
47    
48    =head2 Constructor create
49    
50  =head2 C<$db = create WAIT::Database name =>E<gt> I<name> C<directory =E<gt>> I<dir>C<;>    $db = WAIT::Database->create(
51                                   directory => '/dir/to/database/'
52                                   name      => 'name',
53                                  );
54    
55    Create a new database.
56    
57  =over 10  =over 10
58    
59  =item B<name> I<name>  =item B<name> I<name>
60    
61  mandatory  Mandatory name of database
62    
63  =item B<directory> I<directory>  =item B<directory> I<directory>
64    
# Line 65  directory). Line 69  directory).
69    
70  If given, the database will require unique attributes over all tables.  If given, the database will require unique attributes over all tables.
71    
72  The function will return undef and set C<$@> on failure.  The method will croak on failure.
73    
74    =back
75    
76  =cut  =cut
77    
# Line 73  sub create { Line 79  sub create {
79    my $type = shift;    my $type = shift;
80    my %parm = @_;    my %parm = @_;
81    my $self = {};    my $self = {};
82      bless $self => ref($type) || $type;
83    my $dir  = $parm{directory} || '.';    my $dir  = $parm{directory} || '.';
84    my $name = $parm{name}      or croak "No name specified";    my $name = $parm{name};
85    
86      unless ($name) {
87        croak("No name specified");
88      }
89    
90      unless (-d $dir){
91        croak("Directory '$dir' does not exits: $!");
92      }
93    
94    croak "Directory '$dir' does not exits: $!" unless -d $dir;    if (-d "$dir/$name") {
95    croak "Directory '$name' already exists"    if -d "$dir/$name";      warn "Warning: Directory '$dir/$name' already exists";
96    mkdir "$dir/$name", 0775 or croak "Could not mkdir '$dir/$name': $!";    } else {
97        unless (mkdir "$dir/$name", 0775) {
98          croak("Could not mkdir '$dir/$name': $!");
99        }
100      }
101    
102      $self->{dir}       = $dir;
103    $self->{name}      = $name;    $self->{name}      = $name;
104    $self->{file}      = "$dir/$name";  
105    use Data::Dumper;
106    print Dumper($self);
107    
108      print STDERR "## dir: $dir name: $name path: ",$self->file,"\n";
109    
110      my $env= BerkeleyDB::Env->new(
111                                    -Home => $self->path,
112                                    -Flags => DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB|DB_PRIVATE,
113                                    # Cachesize => 1024*1024*8,
114                                    # Pagesize => 8*1024, # environment doesn't understand Pagesize parameter!
115                                    -Verbose => 1,
116                                    -ErrFile => $self->path."/error.log",
117                                    );
118      unless ($env) {
119        confess("Could not create environment: $BerkeleyDB::Error");
120      }
121    
122      $self->{env} = $env;
123    
124      # apperently (! learned from trial and error) while the Env doesn't
125      # understand Pagesize, the very first table needs to set it up if we
126      # want to deviate from the default. And all tables need to follow
127      # this lead. I'm doing so explicitly, it looks prettier to me
128      $self->{_attr} = BerkeleyDB::Btree->new(
129                                              -Filename => $self->maindbfile,
130                                              -Subname  => "_attr",
131                                              -Flags    => DB_CREATE,
132                                              -Mode     => 0664,
133                                              -Env      => $env,
134                                              $WAIT::Database::Cachesize?(Cachesize => $WAIT::Database::Cachesize):(),
135                                              $WAIT::Database::Pagesize?(Pagesize => $WAIT::Database::Pagesize):(),
136                                             );
137    
138    print STDERR "### created ",$self->maindbfile,"\n";
139    
140      unless (defined($self->{_attr})) {
141        die("Cannot open [".$self->maindbfile."] _attr: $BerkeleyDB::Error");
142      }
143    
144    
145      # Use of BerkeleyDB::Env->new here could maybe some day be a way to
146      # introduce a smart locking mechanism? Whatever... it is currently
147      # kein Thema: remember, that the database has a $self->path which
148      # is a *directory* and there are no berkeley tables in this
149      # directory, but there is one subdirectory in this directory for
150      # *each* *table* object.
151    
152    $self->{uniqueatt} = $parm{uniqueatt};    $self->{uniqueatt} = $parm{uniqueatt};
153    $self->{mode}      = O_CREAT;    $self->{mode}      = O_RDWR;
154    bless $self => ref($type) || $type;    $self;
155  }  }
156    
157    
158  =head2 C<$db = open WAIT::Database name =E<gt>> I<name> C<directory =E<gt>> I<dir>C<;>  =head2 Constructor open
159    
160      $db = WAIT::Database->open(
161                                 name => "foo",
162                                 directory => "bar"
163                                );
164    
165  Open an existing database I<foo> in directory I<bar>.  Open an existing database I<foo> in directory I<bar>.
166    
# Line 99  sub open { Line 171  sub open {
171    my %parm    = @_;    my %parm    = @_;
172    my $dir     = $parm{directory} || '.';    my $dir     = $parm{directory} || '.';
173    my $name    = $parm{name} or croak "No name specified";    my $name    = $parm{name} or croak "No name specified";
174    my $catalog = "$dir/$name/catalog";    my $self = bless {}, ref($type) || $type;
   my $meta    = "$dir/$name/meta";  
   my $self;  
     
   if ($HAVE_STORABLE and -e $catalog  
       and (!-e $meta or -M $meta >= -M $catalog)) {  
     $self = Storable::retrieve($catalog);  
   } else {  
     return undef unless -f $meta;  
175    
176      $self = do $meta;    my $mode = (exists $parm{mode})?$parm{mode}:(O_CREAT | O_RDWR);
177      unless (defined $self) {    $self->{dir}  = $dir; # will be overwritten by the thaw below, but we need it now
178        warn "\ado '$meta' did not work. Mysterious! Reverting to eval `cat $meta`\n";    $self->{name} = $name;
179        sleep(4);  
180        $self = eval `cat $meta`;    my $env;
181    
182      if ($mode & O_RDWR) {
183        my $flags = DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB;
184        warn "setting flags for envorinment 'writing'";
185        $env = BerkeleyDB::Env->new(
186                                    -Home => $self->path,
187                                    -Flags => $flags,
188                                   );
189        unless ($env) {
190          confess("Could not create environment in [".$self->path."]: $BerkeleyDB::Error");
191      }      }
192    }    } else {
193        # in der Datei neo-lieferung.pl habe ich in Punkt 2.6 darueber
194        # lamentiert, dass der Readonly-User kein Environment bekommt.
195        # Es muesste ein Klacks sein, dafuer einen Schalter
196        # bereitzustellen. Kostet mich aber zu viel Denkzeit.
197        # warn "DEBUG: setting env to NIL";
198        $env = "";
199      }
200      # warn "DEBUG: trying to open the database for _attr";
201      my $maindbfile = $self->maindbfile;
202      my $attr = BerkeleyDB::Btree->new(
203                                        -Filename => $maindbfile,
204                                        -Subname  => "_attr",
205                                        $env ? (-Env => $env) : (-Flags => DB_RDONLY, -Home => $self->path),
206                                       );
207      unless (defined($attr)) {
208          use Data::Dumper;
209          print Dumper($attr);
210          croak "Cannot open [$maindbfile] _attr: $BerkeleyDB::Error; mdbf[$maindbfile] env[$env]";
211      }
212      # warn "DEBUG: opened the database for _attr";
213      $attr->db_get(0, my $dat);
214      # warn sprintf "DEBUG: got _attr record. time[%.6f] length(dat)[%d]", Time::HiRes::time(), length $dat;
215      $self = thaw $dat;
216      # warn sprintf "DEBUG: thawed _attr rec. time[%.6f]", Time::HiRes::time();
217      $self->{_attr} = $attr;
218    
219      return unless defined $self;
220    
221      $self->{mode} = $mode;
222      $self->{env}  = $env;
223      $self->{dir}  = $dir; # yes, again
224      $self->{name} = $name;
225      $self->walkncomplete;
226    
   return $self unless defined $self;  
   $self->{mode} = (exists $parm{mode})?$parm{mode}:(O_CREAT | O_RDWR);  
227    $self;    $self;
228  }  }
229    
230    sub walkncomplete {
231      my $self = shift;
232      $self->maindbfile;
233      $self->path;
234      for my $t (values %{$self->{tables} || {}}) {
235        $t->{file} ||= $self->{file};
236        $t->{maindbfile} ||= $self->{maindbfile};
237        $t->{mode} = $self->{mode};
238        for my $ind (values %{$t->{indexes}}) {
239          $ind->{file} ||= $self->{file};
240          $ind->{maindbfile} ||= $self->{maindbfile};
241          $ind->{mode} = $self->{mode};
242        }
243        for my $inv (values %{$t->{inverted}}) {
244          for my $ind (@$inv) {
245            $ind->{file} ||= $self->{file};
246            $ind->{maindbfile} ||= $self->{maindbfile};
247            $ind->{mode} = $self->{mode};
248          }
249        }
250      }
251    }
252    
253  =head2 C<$db-E<gt>dispose;>  =head2 dispose
254    
255  Dispose a database. Remove all associated files. This may fail if the  Dispose a database. Remove all associated files. This may fail if the
256  database or one of its tables is still open. Failure will be indicated  database or one of its tables is still open. Failure will be indicated
257  by a false return value.  by a false return value.
258    
259     $db->dispose;
260    
261     WAIT::Database->dispose(name=>'name', directory=>'/dir/to/db/');
262    
263  =cut  =cut
264    
265  sub dispose {  sub dispose {
266    my $dir;    my $path;
267    
268    if (ref $_[0]) {               # called with instance    if (ref $_[0]) {               # called with instance
269      croak "Database readonly" unless $_[0]->{mode} & (O_CREAT | O_RDWR);      croak "Database readonly" unless $_[0]->{mode} & (O_CREAT | O_RDWR);
270      $dir = $_[0]->{file};      $path = $_[0]->path;
271      $_[0]->close;      $_[0]->close;
272    } else {    } else {
273      my $type = shift;      my $type = shift;
274      my %parm = @_;      my %parm = @_;
275      my $base = $parm{directory} || '.';      my $base = $parm{directory} || '.';
276      my $name = $parm{name}       || croak "No name specified";      my $name = $parm{name}      || croak "No name specified";
277      $dir = "$base/$name";      $path = "$base/$name";
278    }    }
279    croak "No such database '$dir'" unless -e "$dir/meta";    croak "No such database '$path'" unless -e "$path";
280    
281    rmtree($dir, 0, 1);    my $ret = rmtree($path, 0, 1);
282    
283      $ret;
284  }  }
285    
286    
# Line 159  Close a database saving all meta data af Line 292  Close a database saving all meta data af
292    
293  sub close {  sub close {
294    my $self = $_[0];    my $self = $_[0];
295    my $file = $self->{file};    
296    my $table;    for my $table (values %{$self->{tables}}) {
   my $did_save;  
   
   for $table (values %{$self->{tables}}) {  
297      $table->close if ref($table);      $table->close if ref($table);
298    }    }
299    return 1 unless $self->{mode} & (O_RDWR | O_CREAT);    return 1 unless $self->{mode} & (O_RDWR | O_CREAT);
300    
301    if ($HAVE_DATA_DUMPER) {    for my $att (qw(file maindbfile name env)) {
302      my $fh   = new FileHandle "> $file/meta";      delete $self->{$att} || confess "can't delete '$att'";
     if ($fh) {  
       my $dumper = new Data::Dumper [$self],['self'];  
       $fh->print('my ');  
       $fh->print($dumper->Dumpxs);  
       $fh->close;  
       $did_save = 1;  
     } else {  
       croak "Could not open '$file/meta' for writing: $!";  
       return unless $HAVE_STORABLE;  
     }  
303    }    }
304    
305    if ($HAVE_STORABLE) {    my $db = $self->{_attr};
306      if (!eval {Storable::store($self, "$file/catalog")}) {    delete $self->{_attr} || confess "can't delete _attr";
307        fail ("Could not open '$file/catalog' for writing: $!");  
308        return unless $did_save;    my $dat = nfreeze $self;
309      } else {    $db->db_put(0, $dat);
310        $did_save++;  
311      }    #warn "DEBUG: Removing env[$env] before closing database";
312    }    undef $env;
313      #warn "DEBUG: Removed it.";
314    
315    undef $_[0];    undef $_[0];
316    $did_save;    return 1;
317  }  }
318    
319    
320  =head2 C<$db-E<gt>create_table name =E<gt>> I<tname> ... C<;>  =head2 C<$db-E<gt>create_table(name =E<gt>> I<tname>, ... C<);>
321    
322  Create a new table with name I<tname>. All paraeters are passed to  Create a new table with name I<tname>. All parameters are passed to
323  C<WAIT::Table::new> together with a filename to use. The function  C<WAIT::Table-E<gt>new> together with a filename to use. See
324  returns a table handle (C<WAIT::Table::Handle>).  L<WAIT::Table> for which attributes are required. The method returns a
325    table handle (C<WAIT::Table::Handle>).
326    
327  =cut  =cut
328    
329  sub create_table {  sub create_table {
330    my $self = shift;    my $self = shift;
331    my %parm = @_;    my %parm = @_;
332    my $name = $parm{name} || return fail("No name specified");    my $name = $parm{name} or croak "create_table: No name specified";
333    my $file = $self->{file};    my $attr = $parm{attr} or croak "create_table: No attributes specified";
334      my $path = $self->path;
335    
336    croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);    croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);
337    
# Line 216  sub create_table { Line 340  sub create_table {
340    }    }
341    
342    if ($self->{uniqueatt}) {    if ($self->{uniqueatt}) {
343      for (@{$parm{attr}}) {      # attribute names must be uniqe      for (@$attr) {      # attribute names must be uniqe
344        if ($self->{attr}->{$_}) {        if ($self->{attr}->{$_}) {
345          return fail ("Attribute '$_' is not unique")          croak("Attribute '$_' is not unique")
346        }        }
347      }      }
348    }    }
349    $self->{tables}->{$name} = WAIT::Table->new(file     => "$file/$name",    $self->{tables}->{$name} = WAIT::Table->new(file     => "$path/$name",
350                                                database => $self,                                                database => $self,
351                                                  env      => $self->{env},
352                                                  maindbfile  => $self->maindbfile,
353                                                  tablename   => $name,
354                                                %parm);                                                %parm);
355    unless (defined $self->{tables}->{$name}) {# fail gracefully    unless (defined $self->{tables}->{$name}) {# fail gracefully
356      delete $self->{tables}->{$name};      delete $self->{tables}->{$name};
# Line 232  sub create_table { Line 359  sub create_table {
359    
360    if ($self->{uniqueatt}) {    if ($self->{uniqueatt}) {
361      # remember table name for each attribute      # remember table name for each attribute
362      map ($self->{attr}->{$_} = $name, @{$parm{attr}});      map ($self->{attr}->{$_} = $name, @$attr);
363    }    }
364    WAIT::Table::Handle->new($self, $name);    WAIT::Table::Handle->new($self, $name);
365  }  }
366    
367    =head2 maindbfile
368    
369    Name of BerekelyDB database (without path)
370    
371     my $bdb = $db->maindbfile;
372    
373    =cut
374    
375    sub maindbfile {
376      my($self,$path) = @_;
377      return $self->{maindbfile} if $self->{maindbfile};
378      $path ||= $self->path;
379      confess "ALERT: no file argument, no file attribute???" unless $path;
380      $self->{maindbfile} = "etat";
381    }
382    
383    =head2 path
384    
385    Path to database
386    
387     my $db_path = $db->path;
388    
389    =cut
390    
391    sub path {
392      my $self = shift;
393      return $self->{path} if $self->{path};
394      confess("no attribut dir?") unless $self->{dir};
395      confess("no attribut name?") unless $self->{name};
396      $self->{path} = "$self->{dir}/$self->{name}";
397    }
398    
399  =head2 <$db-E<gt>table name =E<gt>> I<tname>C<;>  =head2 C<$db-E<gt>table(name =E<gt>> I<tname>C<);>
400    
401  Open a new table with name I<tname>. The function  Open a new table with name I<tname>. The method
402  returns a table handle (C<WAIT::Table::Handle).  returns a table handle (C<WAIT::Table::Handle>).
403    
404  =cut  =cut
405    
# Line 271  sub table { Line 429  sub table {
429  }  }
430    
431    
432  =head2 C<$db-E<gt>drop  name =E<gt>> I<tname>C<;>  =head2 C<$db-E<gt>drop(name =E<gt>> I<tname>C<);>
433    
434  Drop the table named I<tname>. The table should be closed before  Drop the table named I<tname>. The table should be closed before
435  calling B<drop>.  calling B<drop>.
# Line 300  sub drop_table { Line 458  sub drop_table {
458  }  }
459    
460    
 package WAIT::Table::Handle;  
   
 use Carp;  
   
   
 sub new {  
   my ($type, $database, $name) = @_;  
   
   bless [$database, $name], $type;  
 }  
   
   
 sub AUTOLOAD {  
   my $func = $WAIT::Table::Handle::AUTOLOAD; $func =~ s/.*:://;  
   my $self = $_[0];  
   my ($database, $name) = @{$self};  
   if (defined $database->{tables}->{$name}) {  
     if ($func eq 'drop') {  
       $database->drop_table(name => $name);  
       undef $_[0];  
       1;  
     } elsif ($func ne 'DESTROY') {  
       shift @_;  
       if ($func eq 'open') {  
         $database->{tables}->{$name}->$func(mode => $database->{mode}, @_);  
       } else {  
         $database->{tables}->{$name}->$func(@_);  
       }  
     }  
   } else {  
     return fail("Invalid handle\n");  
   }  
 }  
   
   
461  1;  1;
462    
463    

Legend:
Removed from v.11  
changed lines
  Added in v.108

  ViewVC Help
Powered by ViewVC 1.1.26