/[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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 114 - (show annotations)
Tue Jul 13 21:27:27 2004 UTC (19 years, 9 months ago) by dpavlin
File size: 12382 byte(s)
enought for today, still not passing all tests

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

Properties

Name Value
cvs2svn:cvs-rev 1.2

  ViewVC Help
Powered by ViewVC 1.1.26