/[wait]/branches/unido/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 /branches/unido/lib/WAIT/Database.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 106 - (show annotations)
Tue Jul 13 12:22:09 2004 UTC (19 years, 10 months ago) by dpavlin
File size: 11831 byte(s)
Changes made by Andreas J. Koenig <andreas.koenig(at)anima.de> for Unido project

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
39 #$WAIT::Database::Pagesize = 1*1024;
40 #$WAIT::Database::Cachesize = 4*1024*1024;
41
42 # use autouse Carp => qw( croak($) );
43
44 =head2 Constructor create
45
46 $db = WAIT::Database->create(
47 name => <name>,
48 directory => <dir>
49 );
50
51 Create a new database.
52
53 =over 10
54
55 =item B<name> I<name>
56
57 mandatory
58
59 =item B<directory> I<directory>
60
61 Directory which should contain the database (defaults to the current
62 directory).
63
64 =item B<uniqueatt> I<true>
65
66 If given, the database will require unique attributes over all tables.
67
68 The method will croak on failure.
69
70 =back
71
72 =cut
73
74 sub create {
75 my $type = shift;
76 my %parm = @_;
77 my $self = {};
78 bless $self => ref($type) || $type;
79 my $dir = $parm{directory} || '.';
80 my $name = $parm{name};
81
82 unless ($name) {
83 croak("No name specified");
84 }
85
86 unless (-d $dir){
87 croak("Directory '$dir' does not exits: $!");
88 }
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->{name} = $name;
99 $self->{dir} = $dir;
100 my $env= BerkeleyDB::Env->new(
101 Home => $self->file,
102 Flags => DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB|DB_PRIVATE,
103 # Cachesize => 1024*1024*8,
104 # Pagesize => 8*1024, # environment doesn't understand Pagesize parameter!
105 Verbose => 1,
106 ErrFile => $self->file."/errorlog",
107 );
108 unless ($env) {
109 require Carp;
110 Carp::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 ) or die("Cannot open _attr: $BerkeleyDB::Error");
128
129
130 # Use of BerkeleyDB::Env->new here could maybe some day be a way to
131 # introduce a smart locking mechanism? Whatever... it is currently
132 # kein Thema: remember, that the database has a $self->file which
133 # is a *directory* and there are no berkeley tables in this
134 # directory, but there is one subdirectory in this directory for
135 # *each* *table* object.
136
137 $self->{uniqueatt} = $parm{uniqueatt};
138 $self->{mode} = O_RDWR;
139 $self;
140 }
141
142
143 =head2 Constructor open
144
145 $db = WAIT::Database->open(
146 name => "foo",
147 directory => "bar"
148 );
149
150 Open an existing database I<foo> in directory I<bar>.
151
152 =cut
153
154 sub open {
155 my $type = shift;
156 my %parm = @_;
157 my $dir = $parm{directory} || '.';
158 my $name = $parm{name} or croak "No name specified";
159 my $self = bless {}, ref($type) || $type;
160
161 my $mode = (exists $parm{mode})?$parm{mode}:(O_CREAT | O_RDWR);
162 $self->{dir} = $dir; # will be overwritten by the thaw below, but we need it now
163 $self->{name} = $name;
164 my($env);
165 if ($mode & O_RDWR) {
166 my $flags = DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB;
167 warn "setting flags for envorinment 'writing'";
168 $env = BerkeleyDB::Env->new(
169 Home => $self->file,
170 Flags => $flags,
171 );
172 unless ($env) {
173 require Carp;
174 Carp::confess("Could not create environment: $BerkeleyDB::Error");
175 }
176 } else {
177 # in der Datei neo-lieferung.pl habe ich in Punkt 2.6 darueber
178 # lamentiert, dass der Readonly-User kein Environment bekommt.
179 # Es muesste ein Klacks sein, dafuer einen Schalter
180 # bereitzustellen. Kostet mich aber zu viel Denkzeit.
181 # warn "DEBUG: setting env to NIL";
182 $env = "";
183 }
184 # warn "DEBUG: trying to open the database for _attr";
185 my $maindbfile = $self->maindbfile;
186 my $attr = BerkeleyDB::Btree->new(
187 Filename => $maindbfile,
188 Subname => "_attr",
189 $env ? (Env => $env) : (Flags => DB_RDONLY),
190 )
191 or die "Cannot open _attr: $BerkeleyDB::Error; mdbf[$maindbfile] env[$env]";
192 # warn "DEBUG: opened the database for _attr";
193 $attr->db_get(0, my $dat);
194 # warn sprintf "DEBUG: got _attr record. time[%.6f] length(dat)[%d]", Time::HiRes::time(), length $dat;
195 $self = thaw $dat;
196 # warn sprintf "DEBUG: thawed _attr rec. time[%.6f]", Time::HiRes::time();
197 $self->{_attr} = $attr;
198
199 return unless defined $self;
200
201 $self->{mode} = $mode;
202 $self->{env} = $env;
203 $self->{dir} = $dir; # yes, again
204 $self->{name} = $name;
205 $self->walkncomplete;
206
207 $self;
208 }
209
210 sub walkncomplete {
211 my($self) = @_;
212 $self->maindbfile;
213 $self->file;
214 for my $t (values %{$self->{tables} || {}}) {
215 $t->{file} ||= $self->{file};
216 $t->{maindbfile} ||= $self->{maindbfile};
217 $t->{mode} = $self->{mode};
218 for my $ind (values %{$t->{indexes}}) {
219 $ind->{file} ||= $self->{file};
220 $ind->{maindbfile} ||= $self->{maindbfile};
221 $ind->{mode} = $self->{mode};
222 }
223 for my $inv (values %{$t->{inverted}}) {
224 for my $ind (@$inv) {
225 $ind->{file} ||= $self->{file};
226 $ind->{maindbfile} ||= $self->{maindbfile};
227 $ind->{mode} = $self->{mode};
228 }
229 }
230 }
231 }
232
233 =head2 C<$db-E<gt>dispose;>
234
235 Dispose a database. Remove all associated files. This may fail if the
236 database or one of its tables is still open. Failure will be indicated
237 by a false return value.
238
239 =cut
240
241 sub dispose {
242 my $dir;
243
244 if (ref $_[0]) { # called with instance
245 croak "Database readonly" unless $_[0]->{mode} & (O_CREAT | O_RDWR);
246 $dir = $_[0]->file;
247 $_[0]->close;
248 } else {
249 my $type = shift;
250 my %parm = @_;
251 my $base = $parm{directory} || '.';
252 my $name = $parm{name} || croak "No name specified";
253 $dir = "$base/$name";
254 }
255 croak "No such database '$dir'" unless -e "$dir/meta";
256
257 #warn "Running rmtree on dir[$dir]";
258 my $ret = rmtree($dir, 0, 1);
259 #warn "rmtree returned[$ret]";
260 $ret;
261 }
262
263
264 =head2 C<$db-E<gt>close;>
265
266 Close a database saving all meta data after closing all associated tables.
267
268 =cut
269
270 sub close {
271 my $self = $_[0];
272 my $did_save;
273
274 for my $table (values %{$self->{tables}}) {
275 $table->close if ref($table);
276 }
277 return 1 unless $self->{mode} & (O_RDWR | O_CREAT);
278
279 my $env = delete $self->{env}; # core dump land
280 my $db = delete $self->{_attr}; # core dump land
281 for my $att (qw(file maindbfile name)) {
282 delete $self->{$att};
283 }
284
285 my $dat = nfreeze $self;
286 $db->db_put(0, $dat);
287
288 warn "DEBUG: Removing env[$env] before closing database";
289 undef $env;
290 warn "DEBUG: Removed it.";
291
292 undef $_[0];
293 $did_save;
294 }
295
296
297 =head2 C<$db-E<gt>create_table(name =E<gt>> I<tname>, ... C<);>
298
299 Create a new table with name I<tname>. All parameters are passed to
300 C<WAIT::Table-E<gt>new> together with a filename to use. See
301 L<WAIT::Table> for which attributes are required. The method returns a
302 table handle (C<WAIT::Table::Handle>).
303
304 =cut
305
306 sub create_table {
307 my $self = shift;
308 my %parm = @_;
309 my $name = $parm{name} or croak "create_table: No name specified";
310 my $attr = $parm{attr} or croak "create_table: No attributes specified";
311 my $file = $self->file;
312
313 croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);
314
315 if (defined $self->{tables}->{$name}) {
316 die "Table '$name' already exists";
317 }
318
319 if ($self->{uniqueatt}) {
320 for (@$attr) { # attribute names must be uniqe
321 if ($self->{attr}->{$_}) {
322 croak("Attribute '$_' is not unique")
323 }
324 }
325 }
326 $self->{tables}->{$name} = WAIT::Table->new(file => "$file/$name",
327 database => $self,
328 env => $self->{env},
329 maindbfile => $self->maindbfile,
330 tablename => $name,
331 %parm);
332 unless (defined $self->{tables}->{$name}) {# fail gracefully
333 delete $self->{tables}->{$name};
334 return undef;
335 }
336
337 if ($self->{uniqueatt}) {
338 # remember table name for each attribute
339 map ($self->{attr}->{$_} = $name, @$attr);
340 }
341 WAIT::Table::Handle->new($self, $name);
342 }
343
344 sub maindbfile {
345 my($self,$file) = @_;
346 return $self->{maindbfile} if $self->{maindbfile};
347 $file ||= $self->file;
348 die "ALERT: no file argument, no file attribute???" unless $file;
349 $self->{maindbfile} = "$file/etat";
350 }
351
352 sub file {
353 my($self) = @_;
354 return $self->{file} if $self->{file};
355 require Carp;
356 Carp::confess("no attribut dir?") unless $self->{dir};
357 Carp::confess("no attribut name?") unless $self->{name};
358 $self->{file} = "$self->{dir}/$self->{name}";
359 }
360
361 =head2 C<$db-E<gt>table(name =E<gt>> I<tname>C<);>
362
363 Open a new table with name I<tname>. The method
364 returns a table handle (C<WAIT::Table::Handle>).
365
366 =cut
367
368 sub sync {
369 my $self = shift;
370
371 for (values %{$self->{tables}}) {
372 $_->sync;
373 }
374 }
375
376 sub table {
377 my $self = shift;
378 my %parm = @_;
379 my $name = $parm{name} or croak "No name specified";
380
381 if (defined $self->{tables}->{$name}) {
382 if (exists $parm{mode}) {
383 $self->{tables}->{$name}->{mode} = $parm{mode};
384 } else {
385 $self->{tables}->{$name}->{mode} = $self->{mode};
386 }
387 WAIT::Table::Handle->new($self,$name);
388 } else {
389 croak "No such table '$name'";
390 }
391 }
392
393
394 =head2 C<$db-E<gt>drop(name =E<gt>> I<tname>C<);>
395
396 Drop the table named I<tname>. The table should be closed before
397 calling B<drop>.
398
399 =cut
400
401 sub drop_table {
402 my $self = shift;
403 my %parm = @_;
404 my $name = $parm{name} or croak "No name specified";
405
406 croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR);
407 if (!defined $self->{tables}->{$name}) {
408 croak "Table '$name' does not exist";
409 }
410 $self->{tables}->{$name}->drop;
411
412 if ($self->{uniqueatt}) {
413 # recycle attribute names
414 for (keys %{$self->{attr}}) {
415 delete $self->{attr}->{$_} if $self->{attr}->{$_} eq $name;
416 }
417 }
418 undef $self->{tables}->{$name}; # Call WAIT::Table::DESTROY here;
419 1;
420 }
421
422
423 1;
424
425
426 =head1 AUTHOR
427
428 Ulrich Pfeifer E<lt>F<pfeifer@ls6.informatik.uni-dortmund.de>E<gt>
429
430 =cut
431
432

  ViewVC Help
Powered by ViewVC 1.1.26