1 |
# -*- Mode: Cperl -*- |
2 |
# Database -- |
3 |
# ITIID : $ITI$ $Header $__Header$ |
4 |
# Author : Ulrich Pfeifer |
5 |
# Created On : Thu Aug 8 09:44:13 1996 |
6 |
# Last Modified By: Ulrich Pfeifer |
7 |
# Last Modified On: Sun May 30 18:34:08 1999 |
8 |
# Language : CPerl |
9 |
# Update Count : 250 |
10 |
# Status : Unknown, Use with caution! |
11 |
# |
12 |
# Copyright (c) 1996-1997, Ulrich Pfeifer |
13 |
# |
14 |
|
15 |
=head1 NAME |
16 |
|
17 |
WAIT::Database - Module fo maintaining WAIT databases |
18 |
|
19 |
=head1 SYNOPSIS |
20 |
|
21 |
require WAIT::Database; |
22 |
|
23 |
=head1 DESCRIPTION |
24 |
|
25 |
The modules handles creating, opening, and deleting of databases and |
26 |
tables. |
27 |
|
28 |
=cut |
29 |
|
30 |
package WAIT::Database; |
31 |
|
32 |
use strict; |
33 |
use FileHandle (); |
34 |
use File::Path qw(rmtree); |
35 |
use WAIT::Table (); |
36 |
use Fcntl; |
37 |
use Carp; # will use autouse later |
38 |
# use autouse Carp => qw( croak($) ); |
39 |
my ($HAVE_DATA_DUMPER, $HAVE_STORABLE); |
40 |
|
41 |
BEGIN { |
42 |
eval { require Data::Dumper }; |
43 |
$HAVE_DATA_DUMPER = 1 if $@ eq ''; |
44 |
eval { require Storable }; |
45 |
$HAVE_STORABLE = 1 if $@ eq ''; |
46 |
$HAVE_DATA_DUMPER || $HAVE_STORABLE || |
47 |
die "Could not find Data::Dumper nor Storable"; |
48 |
$Storable::forgive_me = 1; |
49 |
} |
50 |
|
51 |
|
52 |
=head2 Constructor create |
53 |
|
54 |
$db = WAIT::Database->create( |
55 |
name => <name>, |
56 |
directory => <dir> |
57 |
); |
58 |
|
59 |
Create a new database. |
60 |
|
61 |
=over 10 |
62 |
|
63 |
=item B<name> I<name> |
64 |
|
65 |
mandatory |
66 |
|
67 |
=item B<directory> I<directory> |
68 |
|
69 |
Directory which should contain the database (defaults to the current |
70 |
directory). |
71 |
|
72 |
=item B<uniqueatt> I<true> |
73 |
|
74 |
If given, the database will require unique attributes over all tables. |
75 |
|
76 |
The method will croak on failure. |
77 |
|
78 |
=back |
79 |
|
80 |
=cut |
81 |
|
82 |
sub create { |
83 |
my $type = shift; |
84 |
my %parm = @_; |
85 |
my $self = {}; |
86 |
my $dir = $parm{directory} || '.'; |
87 |
my $name = $parm{name}; |
88 |
|
89 |
unless ($name) { |
90 |
croak("No name specified"); |
91 |
} |
92 |
|
93 |
unless (-d $dir){ |
94 |
croak("Directory '$dir' does not exits: $!"); |
95 |
} |
96 |
|
97 |
if (-d "$dir/$name") { |
98 |
warn "Warning: Directory '$dir/$name' already exists"; |
99 |
} else { |
100 |
unless (mkdir "$dir/$name", 0775) { |
101 |
croak("Could not mkdir '$dir/$name': $!"); |
102 |
} |
103 |
} |
104 |
|
105 |
$self->{name} = $name; |
106 |
$self->{file} = "$dir/$name"; |
107 |
$self->{uniqueatt} = $parm{uniqueatt}; |
108 |
$self->{mode} = O_CREAT; |
109 |
bless $self => ref($type) || $type; |
110 |
} |
111 |
|
112 |
|
113 |
=head2 Constructor open |
114 |
|
115 |
$db = WAIT::Database->open( |
116 |
name => "foo", |
117 |
directory => "bar" |
118 |
); |
119 |
|
120 |
Open an existing database I<foo> in directory I<bar>. |
121 |
|
122 |
=cut |
123 |
|
124 |
sub open { |
125 |
my $type = shift; |
126 |
my %parm = @_; |
127 |
my $dir = $parm{directory} || '.'; |
128 |
my $name = $parm{name} or croak "No name specified"; |
129 |
my $catalog = "$dir/$name/catalog"; |
130 |
my $meta = "$dir/$name/meta"; |
131 |
my $self; |
132 |
|
133 |
if ($HAVE_STORABLE and -e $catalog |
134 |
and (!-e $meta or -M $meta >= -M $catalog)) { |
135 |
$self = Storable::retrieve($catalog); |
136 |
} else { |
137 |
return undef unless -f $meta; |
138 |
|
139 |
$self = do $meta; |
140 |
unless (defined $self) { |
141 |
warn "do '$meta' did not work. Mysterious! Reverting to eval `cat $meta`"; |
142 |
sleep(4); |
143 |
$self = eval `cat $meta`; |
144 |
} |
145 |
} |
146 |
|
147 |
return unless defined $self; |
148 |
$self->{mode} = (exists $parm{mode})?$parm{mode}:(O_CREAT | O_RDWR); |
149 |
$self; |
150 |
} |
151 |
|
152 |
|
153 |
=head2 C<$db-E<gt>dispose;> |
154 |
|
155 |
Dispose a database. Remove all associated files. This may fail if the |
156 |
database or one of its tables is still open. Failure will be indicated |
157 |
by a false return value. |
158 |
|
159 |
=cut |
160 |
|
161 |
sub dispose { |
162 |
my $dir; |
163 |
|
164 |
if (ref $_[0]) { # called with instance |
165 |
croak "Database readonly" unless $_[0]->{mode} & (O_CREAT | O_RDWR); |
166 |
$dir = $_[0]->{file}; |
167 |
$_[0]->close; |
168 |
} else { |
169 |
my $type = shift; |
170 |
my %parm = @_; |
171 |
my $base = $parm{directory} || '.'; |
172 |
my $name = $parm{name} || croak "No name specified"; |
173 |
$dir = "$base/$name"; |
174 |
} |
175 |
croak "No such database '$dir'" unless -e "$dir/meta"; |
176 |
|
177 |
#warn "Running rmtree on dir[$dir]"; |
178 |
my $ret = rmtree($dir, 0, 1); |
179 |
#warn "rmtree returned[$ret]"; |
180 |
$ret; |
181 |
} |
182 |
|
183 |
|
184 |
=head2 C<$db-E<gt>close;> |
185 |
|
186 |
Close a database saving all meta data after closing all associated tables. |
187 |
|
188 |
=cut |
189 |
|
190 |
sub close { |
191 |
my $self = $_[0]; |
192 |
my $file = $self->{file}; |
193 |
my $table; |
194 |
my $did_save; |
195 |
|
196 |
for $table (values %{$self->{tables}}) { |
197 |
$table->close if ref($table); |
198 |
} |
199 |
return 1 unless $self->{mode} & (O_RDWR | O_CREAT); |
200 |
|
201 |
if ($HAVE_DATA_DUMPER) { |
202 |
my $fh = new FileHandle "> $file/meta"; |
203 |
if ($fh) { |
204 |
my $dumper = new Data::Dumper [$self],['self']; |
205 |
$fh->print('my '); |
206 |
$fh->print($dumper->Dumpxs); |
207 |
$fh->close; |
208 |
$did_save = 1; |
209 |
} else { |
210 |
croak "Could not open '$file/meta' for writing: $!"; |
211 |
# never reached: return unless $HAVE_STORABLE; |
212 |
} |
213 |
} |
214 |
|
215 |
if ($HAVE_STORABLE) { |
216 |
if (!eval {Storable::store($self, "$file/catalog")}) { |
217 |
croak "Could not open '$file/catalog' for writing: $!"; |
218 |
# never reached: return unless $did_save; |
219 |
} else { |
220 |
$did_save++; |
221 |
} |
222 |
} |
223 |
undef $_[0]; |
224 |
$did_save; |
225 |
} |
226 |
|
227 |
|
228 |
=head2 C<$db-E<gt>create_table(name =E<gt>> I<tname>, ... C<);> |
229 |
|
230 |
Create a new table with name I<tname>. All parameters are passed to |
231 |
C<WAIT::Table-E<gt>new> together with a filename to use. See |
232 |
L<WAIT::Table> for which attributes are required. The method returns a |
233 |
table handle (C<WAIT::Table::Handle>). |
234 |
|
235 |
=cut |
236 |
|
237 |
sub create_table { |
238 |
my $self = shift; |
239 |
my %parm = @_; |
240 |
my $name = $parm{name} or croak "create_table: No name specified"; |
241 |
my $attr = $parm{attr} or croak "create_table: No attributes specified"; |
242 |
my $file = $self->{file}; |
243 |
|
244 |
croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR); |
245 |
|
246 |
if (defined $self->{tables}->{$name}) { |
247 |
die "Table '$name' already exists"; |
248 |
} |
249 |
|
250 |
if ($self->{uniqueatt}) { |
251 |
for (@$attr) { # attribute names must be uniqe |
252 |
if ($self->{attr}->{$_}) { |
253 |
croak("Attribute '$_' is not unique") |
254 |
} |
255 |
} |
256 |
} |
257 |
$self->{tables}->{$name} = WAIT::Table->new(file => "$file/$name", |
258 |
database => $self, |
259 |
%parm); |
260 |
unless (defined $self->{tables}->{$name}) {# fail gracefully |
261 |
delete $self->{tables}->{$name}; |
262 |
return undef; |
263 |
} |
264 |
|
265 |
if ($self->{uniqueatt}) { |
266 |
# remember table name for each attribute |
267 |
map ($self->{attr}->{$_} = $name, @$attr); |
268 |
} |
269 |
WAIT::Table::Handle->new($self, $name); |
270 |
} |
271 |
|
272 |
|
273 |
=head2 C<$db-E<gt>table(name =E<gt>> I<tname>C<);> |
274 |
|
275 |
Open a new table with name I<tname>. The method |
276 |
returns a table handle (C<WAIT::Table::Handle). |
277 |
|
278 |
=cut |
279 |
|
280 |
sub sync { |
281 |
my $self = shift; |
282 |
|
283 |
for (values %{$self->{tables}}) { |
284 |
$_->sync; |
285 |
} |
286 |
} |
287 |
|
288 |
sub table { |
289 |
my $self = shift; |
290 |
my %parm = @_; |
291 |
my $name = $parm{name} or croak "No name specified"; |
292 |
|
293 |
if (defined $self->{tables}->{$name}) { |
294 |
if (exists $parm{mode}) { |
295 |
$self->{tables}->{$name}->{mode} = $parm{mode}; |
296 |
} else { |
297 |
$self->{tables}->{$name}->{mode} = $self->{mode}; |
298 |
} |
299 |
WAIT::Table::Handle->new($self,$name); |
300 |
} else { |
301 |
croak "No such table '$name'"; |
302 |
} |
303 |
} |
304 |
|
305 |
|
306 |
=head2 C<$db-E<gt>drop(name =E<gt>> I<tname>C<);> |
307 |
|
308 |
Drop the table named I<tname>. The table should be closed before |
309 |
calling B<drop>. |
310 |
|
311 |
=cut |
312 |
|
313 |
sub drop_table { |
314 |
my $self = shift; |
315 |
my %parm = @_; |
316 |
my $name = $parm{name} or croak "No name specified"; |
317 |
|
318 |
croak "Database readonly" unless $self->{mode} & (O_CREAT | O_RDWR); |
319 |
if (!defined $self->{tables}->{$name}) { |
320 |
croak "Table '$name' does not exist"; |
321 |
} |
322 |
$self->{tables}->{$name}->drop; |
323 |
|
324 |
if ($self->{uniqueatt}) { |
325 |
# recycle attribute names |
326 |
for (keys %{$self->{attr}}) { |
327 |
delete $self->{attr}->{$_} if $self->{attr}->{$_} eq $name; |
328 |
} |
329 |
} |
330 |
undef $self->{tables}->{$name}; # Call WAIT::Table::DESTROY here; |
331 |
1; |
332 |
} |
333 |
|
334 |
|
335 |
1; |
336 |
|
337 |
|
338 |
=head1 AUTHOR |
339 |
|
340 |
Ulrich Pfeifer E<lt>F<pfeifer@ls6.informatik.uni-dortmund.de>E<gt> |
341 |
|
342 |
=cut |
343 |
|
344 |
|