/[wait]/cvs-head/lib/WAIT/Table.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 /cvs-head/lib/WAIT/Table.pm

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

branches/CPAN/lib/WAIT/Table.pm revision 13 by ulpfr, Fri Apr 28 15:42:44 2000 UTC cvs-head/lib/WAIT/Table.pm revision 85 by ulpfr, Fri May 3 16:16:10 2002 UTC
# Line 4  Line 4 
4  # Author          : Ulrich Pfeifer  # Author          : Ulrich Pfeifer
5  # Created On      : Thu Aug  8 13:05:10 1996  # Created On      : Thu Aug  8 13:05:10 1996
6  # Last Modified By: Ulrich Pfeifer  # Last Modified By: Ulrich Pfeifer
7  # Last Modified On: Sun May 30 20:42:30 1999  # Last Modified On: Sat Apr 27 17:20:31 2002
8  # Language        : CPerl  # Language        : CPerl
9  # Update Count    : 56  # Update Count    : 172
10  # Status          : Unknown, Use with caution!  # Status          : Unknown, Use with caution!
11  #  #
12  # Copyright (c) 1996-1997, Ulrich Pfeifer  # Copyright (c) 1996-1997, Ulrich Pfeifer
# Line 32  require WAIT::Parse::Base; Line 32  require WAIT::Parse::Base;
32  use strict;  use strict;
33  use Carp;  use Carp;
34  # use autouse Carp => qw( croak($) );  # use autouse Carp => qw( croak($) );
35  use DB_File;  use BerkeleyDB;
36  use Fcntl;  use Fcntl;
37    use LockFile::Simple ();
38    
39  my $USE_RECNO = 0;  my $USE_RECNO = 0;
40    
# Line 158  sub new { Line 159  sub new {
159    }    }
160    
161    $self->{file}     = $parm{file}     or croak "No file specified";    $self->{file}     = $parm{file}     or croak "No file specified";
162    if (-d  $self->{file}){    if (-e  $self->{file}){
163      warn "Warning: Directory '$self->{file}' already exists\n";      warn "Warning: file '$self->{file}' already exists\n";
   } elsif (!mkdir($self->{file}, 0775)) {  
     croak "Could not 'mkdir $self->{file}': $!\n";  
164    }    }
165    
166    $self->{djk}      = $parm{djk}      if defined $parm{djk};    $self->{djk}      = $parm{djk}      if defined $parm{djk};
167    $self->{layout}   = $parm{layout} || new WAIT::Parse::Base;    $self->{layout}   = $parm{layout} || new WAIT::Parse::Base;
168    $self->{access}   = $parm{access} if defined $parm{access};    $self->{access}   = $parm{access} if defined $parm{access};
# Line 171  sub new { Line 171  sub new {
171    $self->{indexes}  = {};    $self->{indexes}  = {};
172    
173    bless $self, $type;    bless $self, $type;
174    
175      # Checking for readers is not necessary, but let's go with the
176      # generic method.
177      $self->getlock(O_RDWR|O_CREAT); # dies when failing
178      
179    # Call create_index() and create_index() for compatibility    # Call create_index() and create_index() for compatibility
180    for (@{$self->{keyset}||[]}) {    for (@{$self->{keyset}||[]}) {
181      #carp "Specification of indexes at table create time is deprecated";      #carp "Specification of indexes at table create time is deprecated";
# Line 188  sub new { Line 193  sub new {
193      }      }
194      $self->create_inverted_index(attribute => $att, pipeline  => \@spec, @opt);      $self->create_inverted_index(attribute => $att, pipeline  => \@spec, @opt);
195    }    }
196    
197    $self;    $self;
198    # end of backwarn compatibility stuff    # end of backwarn compatibility stuff
199  }  }
# Line 215  sub create_index { Line 221  sub create_index {
221    
222    my $name = join '-', @_;    my $name = join '-', @_;
223    $self->{indexes}->{$name} =    $self->{indexes}->{$name} =
224      new WAIT::Index file => $self->{file}.'/'.$name, attr => $_;      new WAIT::Index file => $self->{file}, name => $name, attr => $_;
225  }  }
226    
227  =head2 Creating an inverted index  =head2 Creating an inverted index
# Line 315  Must be called via C<WAIT::Database::dro Line 321  Must be called via C<WAIT::Database::dro
321    
322  sub drop {  sub drop {
323    my $self = shift;    my $self = shift;
324    
325      unless ($self->{write_lock}){
326        warn "Cannot drop table without write lock. Nothing done";
327        return;
328      }
329      
330    if ((caller)[0] eq 'WAIT::Database') { # database knows about this    if ((caller)[0] eq 'WAIT::Database') { # database knows about this
331      $self->close;               # just make sure      $self->close;               # just make sure
332    
333      my $file = $self->{file};      my $file = $self->{file};
334    
335      for (values %{$self->{indexes}}) {      for (values %{$self->{indexes}}) {
336        $_->drop;        $_->drop;
337      }      }
338      unlink "$file/records";      rmdir "$file.read" or warn "Could not rmdir '$file/read'";
339      ! (!-e $file or rmdir $file);      unlink "$file";
340        
341    } else {    } else {
342      croak ref($self)."::drop called directly";      croak ref($self)."::drop called directly";
343    }    }
# Line 363  sub open { Line 377  sub open {
377      }      }
378      require WAIT::InvertedIndex;      require WAIT::InvertedIndex;
379    }    }
380    
381      $self->getlock($self->{mode});
382    
383      my $dbmode = ($self->{mode} & O_CREAT) ? DB_CREATE : 0;
384    unless (defined $self->{dbh}) {    unless (defined $self->{dbh}) {
385      if ($USE_RECNO) {      if ($USE_RECNO) {
386        $self->{dbh} = tie(@{$self->{db}}, 'DB_File', $file,        tie(%{$self->{db}}, 'BerkeleyDB::Recno',
387                           $self->{mode}, 0664, $DB_RECNO);            -Filename => $self->{file},
388              -Subname  => 'records',
389              -Flags    => $dbmode);
390      } else {      } else {
391        $self->{dbh} =        $self->{dbh} =
392          tie(%{$self->{db}}, 'DB_File', $file,          tie(%{$self->{db}}, 'BerkeleyDB::Btree',
393                           $self->{mode}, 0664, $DB_BTREE);              -Filename => $self->{file},
394                -Subname  => 'records',
395                -Mode     => 0664,
396                -Flags    => $dbmode);
397      }      }
398    }    }
399      
400      
401    $self;    $self;
402  }  }
403    
# Line 432  sub insert { Line 457  sub insert {
457    my $tuple = join($;, map($parm{$_} || '', @{$self->{attr}}));    my $tuple = join($;, map($parm{$_} || '', @{$self->{attr}}));
458    my $key;    my $key;
459    my @deleted = keys %{$self->{deleted}};    my @deleted = keys %{$self->{deleted}};
460      my $gotkey = 0;
461    
462    if (@deleted) {    if (@deleted) {
463      $key = pop @deleted;      $key = pop @deleted;
464      delete $self->{deleted}->{$key};      delete $self->{deleted}->{$key};
465        # Sanity check
466        if ($key && $key>0) {
467          $gotkey=1;
468    } else {    } else {
469          warn(sprintf("WAIT database inconsistency during insert ".
470                       "key[%s]: Please rebuild index\n",
471                       $key
472                      ));
473        }
474      }
475      unless ($gotkey) {
476      $key = $self->{nextk}++;      $key = $self->{nextk}++;
477    }    }
478    if ($USE_RECNO) {    if ($USE_RECNO) {
# Line 450  sub insert { Line 486  sub insert {
486        if ($key == $self->{nextk}-1) {        if ($key == $self->{nextk}-1) {
487          $self->{nextk}--;          $self->{nextk}--;
488        } else {        } else {
489            # warn "setting key[$key] deleted during insert";
490          $self->{deleted}->{$key}=1;          $self->{deleted}->{$key}=1;
491        }        }
492        my $idx;        my $idx;
# Line 504  sub delete_by_key { Line 541  sub delete_by_key {
541    my $self  = shift;    my $self  = shift;
542    my $key   = shift;    my $key   = shift;
543    
544      unless ($key) {
545        Carp::cluck "Warning: delete_by_key called without key. Looks like a bug in WAIT?";
546        return;
547      }
548    
549    return $self->{deleted}->{$key} if defined $self->{deleted}->{$key};    return $self->{deleted}->{$key} if defined $self->{deleted}->{$key};
550    my %tuple = $self->fetch($key);    my %tuple = $self->fetch($key);
551    for (values %{$self->{indexes}}) {    for (values %{$self->{indexes}}) {
# Line 520  sub delete_by_key { Line 562  sub delete_by_key {
562        }        }
563      }      }
564    }    }
565      # warn "setting key[$key] deleted during delete_by_key";
566    ++$self->{deleted}->{$key};    ++$self->{deleted}->{$key};
567  }  }
568    
569  sub delete {  sub delete {
570    my $self  = shift;    my $self  = shift;
571    my $tkey = $self->have(@_);    my $tkey = $self->have(@_);
572      # warn "tkey[$tkey]\@_[@_]";
573    defined $tkey && $self->delete_by_key($tkey, @_);    defined $tkey && $self->delete_by_key($tkey, @_);
574  }  }
575    
576  sub unpack {  sub unpack {
577    my $self = shift;    my($self, $tuple) = @_;
578    my $tuple = shift;  
579      unless (defined $tuple){
580        # require Carp; # unfortunately gives us "bizarre copy...." :-(((((
581        warn("Debug: somebody called unpack without argument tuple!");
582        return;
583      }
584    
585    my $att;    my $att;
586    my @result;    my @result;
# Line 544  sub unpack { Line 592  sub unpack {
592    @result;    @result;
593  }  }
594    
595    sub set {
596      my ($self, $iattr, $value) = @_;
597      
598      unless ($self->{write_lock}){
599        warn "Cannot set iattr[$iattr] without write lock. Nothing done";
600        return;
601      }
602    
603      # in the rare case that they haven't written a single record yet, we
604      # make sure, the inverted inherits our $self->{mode}:
605      defined $self->{db} or $self->open;
606    
607      for my $att (keys %{$self->{inverted}}) {
608        if ($] > 5.003) {         # avoid bug in perl up to 5.003_05
609          my $idx;
610          for $idx (@{$self->{inverted}->{$att}}) {
611            $idx->set($iattr, $value);
612          }
613        } else {
614          map $_->set($iattr, $value), @{$self->{inverted}->{$att}};
615        }
616      }
617    
618      1;
619    }
620    
621  sub close {  sub close {
622    my $self = shift;    my $self = shift;
623    
624    if (exists $self->{'access'}) {    if (exists $self->{'access'}) {
625      eval {$self->{'access'}->close}; # dont bother if not opened      eval {$self->{'access'}->close}; # dont bother if not opened
626    }    }
627    for (values %{$self->{indexes}}) {    if ($WAIT::Index::VERSION) {
628      $_->close();      for (values %{$self->{indexes}}) {
629          $_->close();
630        }
631    }    }
632    if (defined $self->{inverted}) {    if (defined $self->{inverted} && $WAIT::InvertedIndex::VERSION) {
633        # require WAIT::InvertedIndex; Uli: we can avoid closing indexes:
634        # if WAIT::InvertedIndex has not been loaded, they cannot have
635        # been altered so far
636      my $att;      my $att;
637      for $att (keys %{$self->{inverted}}) {      for $att (keys %{$self->{inverted}}) {
638        if ($] > 5.003) {         # avoid bug in perl up to 5.003_05        if ($] > 5.003) {         # avoid bug in perl up to 5.003_05
# Line 577  sub close { Line 656  sub close {
656      delete $self->{db};      delete $self->{db};
657    }    }
658    
659      $self->unlock;
660      
661    1;    1;
662  }  }
663    
664    # Locking
665    #
666    # We allow multiple readers to coexists.  But write access excludes
667    # all read access and vice versa.  In practice read access on tables
668    # open for writing will mostly work ;-)
669    
670    # If a "write" lock is requested, an existing "read" lock will be
671    # released.  If a "read" lock ist requested, an existing "write" lock
672    # will be released.  Requiring a lock already hold has no effect.
673    
674    sub getlock {
675      my ($self, $mode) = @_;
676    
677      # autoclean cleans on DESTROY, stale sends SIGZERO to the owner
678      #
679      my $lockmgr = LockFile::Simple->make(-autoclean => 1, -stale => 1);
680      my $file    = $self->{file};
681      my $lockdir = $self->{file} . '.read';
682    
683      unless (-d $lockdir) {
684        mkdir $lockdir, 0755 or die "Could not mkdir $lockdir: $!";
685      }
686      
687      if ($mode & O_RDWR) {         # Get a write lock.  Release it again
688                                    # and die if there is any valid
689                                    # readers.
690        
691        # Have a write lock already
692        return $self if $self->{write_lock};
693    
694        if ($self->{read_lock}) {   # We are a becoming a writer now. So
695                                    # we release the read lock to avoid
696                                    # blocking ourselves.
697          $self->{read_lock}->release;
698          delete $self->{read_lock};
699        }
700    
701        # Get the preliminary write lock
702        $self->{write_lock} = $lockmgr->lock($self->{file} . '.write')
703          or die "Can't lock '$self->{file}.write'";
704        
705        # If we actually want to write we must check if there are any
706        # readers.  The write lock is confirmed if wen cannot find any
707        # valid readers.
708        
709        local *DIR;
710        opendir DIR, $lockdir or
711          die "Could not opendir '$lockdir': $!";
712        for my $lockfile (grep { -f "$lockdir/$_" } readdir DIR) {
713          # Check if the locks are still valid.  Since we are protected by
714          # a write lock, we could use a plain file.  But we want to use
715          # the stale testing from LockFile::Simple.
716          if (my $lck = $lockmgr->trylock("$lockdir/$lockfile")) {
717            warn "Removing stale lockfile '$lockdir/$lockfile'";
718            $lck->release;
719          } else {                  # Found an active reader, rats!
720            $self->{write_lock}->release;
721            die "Cannot write table '$file' while it's in use";
722          }
723        }
724        closedir DIR;
725      } else {
726        # Have a read lock already
727        return $self if $self->{read_lock};
728    
729        # Get the preliminary write lock to protect the directory
730        # operations.
731    
732        my $write_lock = $lockmgr->lock($self->{file} . '.read/write')
733          or die "Can't lock '$self->{file}.read/write'";
734    
735        # Find a new read slot.  Maybe the plain file would be better?
736        my $id = time;
737        while (-f "$lockdir/$id.lock") { # here assume ".lock" format!
738          $id++;
739        }
740    
741        $self->{read_lock} = $lockmgr->lock("$lockdir/$id")
742          or die "Can't lock '$lockdir/$id'";
743    
744        # We are a reader now. So we release the write lock
745        $write_lock->release;
746      }
747      return $self;
748    }
749    
750    sub unlock {
751      my $self = shift;
752    
753      # Either we have a read or a write lock (or we close the table already)
754      # unless ($self->{read_lock} || $self->{write_lock}) {
755      #   warn "WAIT::Table::unlock: Table aparently hold's no lock"
756      # }
757      if ($self->{write_lock}) {
758        $self->{write_lock}->release();
759        delete $self->{write_lock};
760      }
761      if ($self->{read_lock}) {
762        $self->{read_lock}->release();
763        delete $self->{read_lock};
764      }
765    
766    }
767    
768  sub DESTROY {  sub DESTROY {
769    my $self = shift;    my $self = shift;
770    
771    warn "Table handle destroyed without closing it first"    if ($self->{write_lock} || $self->{read_lock}) {
772      if $self->{db} and $self->{mode}&O_RDWR;      warn "Table handle destroyed without closing it first";
773        $self->unlock;
774      }
775  }  }
776    
777  sub open_scan {  sub open_scan {
# Line 642  sub intervall { Line 829  sub intervall {
829  }  }
830    
831  sub search {  sub search {
832    my $self = shift;    my $self  = shift;
833    my $attr = shift;    my ($query, $attr, $cont, $raw);
834    my $cont = shift;    if (ref $_[0]) {
835    my $raw  = shift;      $query = shift;
836      
837        $attr = $query->{attr};
838        $cont = $query->{cont};
839        $raw  = $query->{raw};
840      } else {
841        require Carp;
842        Carp::cluck("Using three argument search interface is deprecated, use hashref interface instead");
843        $attr = shift;
844        $cont = shift;
845        $raw  = shift;
846        $query = {
847                  attr => $attr,
848                  cont => $cont,
849                  raw  => $raw,
850                 };
851      }
852    
853    my %result;    my %result;
854    
855    defined $self->{db} or $self->open; # require layout    defined $self->{db} or $self->open; # require layout
# Line 655  sub search { Line 859  sub search {
859        my $name = $_->name;        my $name = $_->name;
860        if (exists $raw->{$name} and @{$raw->{$name}}) {        if (exists $raw->{$name} and @{$raw->{$name}}) {
861          my $scale = 1/scalar(@{$raw->{$name}});          my $scale = 1/scalar(@{$raw->{$name}});
862          my %r = $_->search_raw(@{$raw->{$name}});          my %r = $_->search_raw($query, @{$raw->{$name}});
863          my ($key, $val);          my ($key, $val);
864          while (($key, $val) = each %r) {          while (($key, $val) = each %r) {
865            if (exists $result{$key}) {            if (exists $result{$key}) {
# Line 669  sub search { Line 873  sub search {
873    }    }
874    if (defined $cont and $cont ne '') {    if (defined $cont and $cont ne '') {
875      for (@{$self->{inverted}->{$attr}}) {      for (@{$self->{inverted}->{$attr}}) {
876        my %r = $_->search($cont);        my %r = $_->search($query, $cont);
877        my ($key, $val);        my ($key, $val);
878        while (($key, $val) = each %r) {        while (($key, $val) = each %r) {
879          if (exists $result{$key}) {          if (exists $result{$key}) {

Legend:
Removed from v.13  
changed lines
  Added in v.85

  ViewVC Help
Powered by ViewVC 1.1.26