/[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 110 - (show annotations)
Tue Jul 13 19:05:31 2004 UTC (19 years, 10 months ago) by dpavlin
File size: 12326 byte(s)
come cleanup

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 print STDERR "## dir: $dir name: $name\n";
102
103 my $env = BerkeleyDB::Env->new(
104 -Home => $self->path,
105 -Flags => DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB|DB_PRIVATE,
106 # Cachesize => 1024*1024*8,
107 # Pagesize => 8*1024, # environment doesn't understand Pagesize parameter!
108 -Verbose => 1,
109 -ErrFile => $self->path."/error.log",
110 );
111 unless ($env) {
112 confess("Could not create environment: $BerkeleyDB::Error");
113 }
114
115 $self->{env} = $env;
116
117 # apperently (! learned from trial and error) while the Env doesn't
118 # understand Pagesize, the very first table needs to set it up if we
119 # want to deviate from the default. And all tables need to follow
120 # this lead. I'm doing so explicitly, it looks prettier to me
121 $self->{_attr} = BerkeleyDB::Btree->new(
122 -Filename => $self->maindbfile,
123 -Subname => "_attr",
124 -Flags => DB_CREATE,
125 -Mode => 0664,
126 -Env => $env,
127 $WAIT::Database::Cachesize?(Cachesize => $WAIT::Database::Cachesize):(),
128 $WAIT::Database::Pagesize?(Pagesize => $WAIT::Database::Pagesize):(),
129 );
130
131 print STDERR "### created ",$self->maindbfile,"\n";
132
133 unless (defined($self->{_attr})) {
134 die("Cannot open [".$self->maindbfile."] _attr: $BerkeleyDB::Error");
135 }
136
137
138 # Use of BerkeleyDB::Env->new here could maybe some day be a way to
139 # introduce a smart locking mechanism? Whatever... it is currently
140 # kein Thema: remember, that the database has a $self->path which
141 # is a *directory* and there are no berkeley tables in this
142 # directory, but there is one subdirectory in this directory for
143 # *each* *table* object.
144
145 $self->{uniqueatt} = $parm{uniqueatt};
146 $self->{mode} = O_RDWR;
147 $self;
148 }
149
150
151 =head2 Constructor open
152
153 $db = WAIT::Database->open(
154 name => "foo",
155 directory => "bar"
156 );
157
158 Open an existing database I<foo> in directory I<bar>.
159
160 =cut
161
162 sub open {
163 my $type = shift;
164 my %parm = @_;
165 my $dir = $parm{directory} || '.';
166 my $name = $parm{name} or croak "No name specified";
167 my $self = bless {}, ref($type) || $type;
168
169 my $mode = (exists $parm{mode})?$parm{mode}:(O_CREAT | O_RDWR);
170 $self->{dir} = $dir; # will be overwritten by the thaw below, but we need it now
171 $self->{name} = $name;
172
173 my $env;
174
175 if ($mode & O_RDWR) {
176 my $flags = DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB;
177 #warn "setting flags for envorinment 'writing'";
178 $env = BerkeleyDB::Env->new(
179 -Home => $self->path,
180 -Flags => $flags,
181 );
182 unless ($env) {
183 confess("Could not create environment in [".$self->path."]: $BerkeleyDB::Error");
184 }
185 } else {
186 # in der Datei neo-lieferung.pl habe ich in Punkt 2.6 darueber
187 # lamentiert, dass der Readonly-User kein Environment bekommt.
188 # Es muesste ein Klacks sein, dafuer einen Schalter
189 # bereitzustellen. Kostet mich aber zu viel Denkzeit.
190 # warn "DEBUG: setting env to NIL";
191 $env = "";
192 }
193
194 warn "DEBUG: trying to open the database for _attr";
195 my $maindbfile = $self->maindbfile;
196 my $attr = BerkeleyDB::Btree->new(
197 -Filename => $maindbfile,
198 -Subname => "_attr",
199 $env ? (-Env => $env) : (-Flags => DB_RDONLY),
200 );
201 unless (defined($attr)) {
202 croak "Cannot open [$maindbfile] _attr: $BerkeleyDB::Error; mdbf[$maindbfile] env[$env]";
203 }
204
205 #warn "DEBUG: opened the database for _attr";
206 $attr->db_get(0, my $dat);
207 #warn sprintf "DEBUG: got _attr record. time[%.6f] length(dat)[%d]", Time::HiRes::time(), length $dat;
208 $self = thaw $dat;
209 #warn sprintf "DEBUG: thawed _attr rec. time[%.6f]", Time::HiRes::time();
210 $self->{_attr} = $attr;
211
212 return unless defined $self;
213
214 $self->{mode} = $mode;
215 $self->{env} = $env;
216 $self->{dir} = $dir; # yes, again
217 $self->{name} = $name;
218 $self->walkncomplete;
219
220 $self;
221 }
222
223 sub walkncomplete {
224 my $self = shift;
225 $self->maindbfile;
226 $self->path;
227 for my $t (values %{$self->{tables} || {}}) {
228 $t->{file} ||= $self->{file};
229 $t->{maindbfile} ||= $self->{maindbfile};
230 $t->{mode} = $self->{mode};
231 for my $ind (values %{$t->{indexes}}) {
232 $ind->{file} ||= $self->{file};
233 $ind->{maindbfile} ||= $self->{maindbfile};
234 $ind->{mode} = $self->{mode};
235 }
236 for my $inv (values %{$t->{inverted}}) {
237 for my $ind (@$inv) {
238 $ind->{file} ||= $self->{file};
239 $ind->{maindbfile} ||= $self->{maindbfile};
240 $ind->{mode} = $self->{mode};
241 }
242 }
243 }
244 }
245
246
247 =head2 close
248
249 Close a database saving all meta data after closing all associated tables.
250
251 $db->close;
252
253 =cut
254
255 sub close {
256 my $self = shift;
257
258 for my $table (values %{$self->{tables}}) {
259 $table->close if ref($table);
260 }
261 return 1 unless $self->{mode} & (O_RDWR | O_CREAT);
262
263 my $env = $self->{env};
264
265 for my $att (qw(path maindbfile name env)) {
266 delete $self->{$att} || confess "can't delete '$att'";
267 }
268
269 my $db = $self->{_attr};
270 delete $self->{_attr} || confess "can't delete _attr";
271
272 my $dat = nfreeze $self;
273 $db->db_put(0, $dat);
274
275 #warn "DEBUG: Removing env[$env] before closing database";
276 undef $env;
277 #warn "DEBUG: Removed it.";
278
279 undef $_[0];
280 return 1;
281 }
282
283
284 =head2 dispose
285
286 Dispose a database. Remove all associated files. This may fail if the
287 database or one of its tables is still open. Failure will be indicated
288 by a false return value.
289
290 $db->dispose;
291
292 WAIT::Database->dispose(name=>'name', directory=>'/dir/to/db/');
293
294 =cut
295
296 sub dispose {
297 my $self = shift;
298
299 my $path;
300
301 if ($self && ref $self) { # called with instance
302 croak "no mode" unless defined($self->{mode});
303 croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);
304 $path = $self->path;
305 $self->close;
306 } else {
307 my %parm = @_;
308 my $base = $parm{directory} || '.';
309 my $name = $parm{name} || croak "No name specified";
310 $path = "$base/$name";
311 }
312 croak "No such database '$path'" unless -e "$path";
313
314 my $ret = rmtree($path, 0, 1);
315
316 return $ret;
317 }
318
319
320 =head2 C<$db-E<gt>create_table(name =E<gt>> I<tname>, ... C<);>
321
322 Create a new table with name I<tname>. All parameters are passed to
323 C<WAIT::Table-E<gt>new> together with a filename to use. See
324 L<WAIT::Table> for which attributes are required. The method returns a
325 table handle (C<WAIT::Table::Handle>).
326
327 =cut
328
329 sub create_table {
330 my $self = shift;
331 my %parm = @_;
332 my $name = $parm{name} or croak "create_table: No name specified";
333 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);
337
338 if (defined $self->{tables}->{$name}) {
339 die "Table '$name' already exists";
340 }
341
342 if ($self->{uniqueatt}) {
343 for (@$attr) { # attribute names must be uniqe
344 if ($self->{attr}->{$_}) {
345 croak("Attribute '$_' is not unique")
346 }
347 }
348 }
349 $self->{tables}->{$name} = WAIT::Table->new(file => "$path/$name",
350 database => $self,
351 env => $self->{env},
352 maindbfile => $self->maindbfile,
353 tablename => $name,
354 %parm);
355 unless (defined $self->{tables}->{$name}) {# fail gracefully
356 delete $self->{tables}->{$name};
357 return undef;
358 }
359
360 if ($self->{uniqueatt}) {
361 # remember table name for each attribute
362 map ($self->{attr}->{$_} = $name, @$attr);
363 }
364 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 "no path argument or 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 C<$db-E<gt>table(name =E<gt>> I<tname>C<);>
400
401 Open a new table with name I<tname>. The method
402 returns a table handle (C<WAIT::Table::Handle>).
403
404 =cut
405
406 sub sync {
407 my $self = shift;
408
409 for (values %{$self->{tables}}) {
410 $_->sync;
411 }
412 }
413
414 sub table {
415 my $self = shift;
416 my %parm = @_;
417 my $name = $parm{name} or croak "No name specified";
418
419 if (defined $self->{tables}->{$name}) {
420 if (exists $parm{mode}) {
421 $self->{tables}->{$name}->{mode} = $parm{mode};
422 } else {
423 $self->{tables}->{$name}->{mode} = $self->{mode};
424 }
425 WAIT::Table::Handle->new($self,$name);
426 } else {
427 croak "No such table '$name'";
428 }
429 }
430
431
432 =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
435 calling B<drop>.
436
437 =cut
438
439 sub drop_table {
440 my $self = shift;
441 my %parm = @_;
442 my $name = $parm{name} or croak "No name specified";
443
444 croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);
445 if (!defined $self->{tables}->{$name}) {
446 croak "Table '$name' does not exist";
447 }
448 $self->{tables}->{$name}->drop;
449
450 if ($self->{uniqueatt}) {
451 # recycle attribute names
452 for (keys %{$self->{attr}}) {
453 delete $self->{attr}->{$_} if $self->{attr}->{$_} eq $name;
454 }
455 }
456 undef $self->{tables}->{$name}; # Call WAIT::Table::DESTROY here;
457 1;
458 }
459
460
461 1;
462
463
464 =head1 AUTHOR
465
466 Ulrich Pfeifer E<lt>F<pfeifer@ls6.informatik.uni-dortmund.de>E<gt>
467
468 =cut
469
470

Properties

Name Value
cvs2svn:cvs-rev 1.2

  ViewVC Help
Powered by ViewVC 1.1.26