Refactored to _descend to fix the recursion bug
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Engine / DBI.pm
1 package DBM::Deep::Engine::DBI;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 use base 'DBM::Deep::Engine';
9
10 use DBM::Deep::Sector::DBI ();
11 use DBM::Deep::Storage::DBI ();
12
13 sub sector_type { 'DBM::Deep::Sector::DBI' }
14 sub iterator_class { 'DBM::Deep::Iterator::DBI' }
15
16 sub new {
17     my $class = shift;
18     my ($args) = @_;
19
20     $args->{storage} = DBM::Deep::Storage::DBI->new( $args )
21         unless exists $args->{storage};
22
23     my $self = bless {
24         storage => undef,
25     }, $class;
26
27     # Grab the parameters we want to use
28     foreach my $param ( keys %$self ) {
29         next unless exists $args->{$param};
30         $self->{$param} = $args->{$param};
31     }
32
33     return $self;
34 }
35
36 sub setup {
37     my $self = shift;
38     my ($obj) = @_;
39
40     # Default the id to 1. This means that we will be creating a row if there
41     # isn't one. The assumption is that the row_id=1 cannot never be deleted. I
42     # don't know if this is a good assumption.
43     $obj->{base_offset} ||= 1;
44
45     my ($rows) = $self->storage->read_from(
46         refs => $obj->_base_offset,
47         qw( ref_type ),
48     );
49
50     # We don't have a row yet.
51     unless ( @$rows ) {
52         $self->storage->write_to(
53             refs => $obj->_base_offset,
54             ref_type => $obj->_type,
55         );
56     }
57
58     my $sector = DBM::Deep::Sector::DBI::Reference->new({
59         engine => $self,
60         offset => $obj->_base_offset,
61     });
62 }
63
64 sub read_value {
65     my $self = shift;
66     my ($obj, $key) = @_;
67
68     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
69         or return;
70
71 #    if ( $sector->staleness != $obj->_staleness ) {
72 #        return;
73 #    }
74
75 #    my $key_md5 = $self->_apply_digest( $key );
76
77     my $value_sector = $sector->get_data_for({
78         key => $key,
79 #        key_md5    => $key_md5,
80         allow_head => 1,
81     });
82
83     unless ( $value_sector ) {
84         $value_sector = DBM::Deep::Sector::DBI::Scalar->new({
85             engine    => $self,
86             data      => undef,
87             data_type => 'S',
88         });
89
90         $sector->write_data({
91 #            key_md5 => $key_md5,
92             key     => $key,
93             value   => $value_sector,
94         });
95     }
96
97     return $value_sector->data;
98 }
99
100 sub get_classname {
101     my $self = shift;
102     my ($obj) = @_;
103
104     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
105         or return;
106
107     return $sector->get_classname;
108 }
109
110 sub make_reference {
111     my $self = shift;
112     my ($obj, $old_key, $new_key) = @_;
113
114     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
115         or return;
116
117 #    if ( $sector->staleness != $obj->_staleness ) {
118 #        return;
119 #    }
120
121     my $value_sector = $sector->get_data_for({
122         key        => $old_key,
123         allow_head => 1,
124     });
125
126     unless ( $value_sector ) {
127         $value_sector = DBM::Deep::Sector::DBI::Scalar->new({
128             engine => $self,
129             data   => undef,
130         });
131
132         $sector->write_data({
133             key     => $old_key,
134             value   => $value_sector,
135         });
136     }
137
138     if ( $value_sector->isa( 'DBM::Deep::Sector::DBI::Reference' ) ) {
139         $sector->write_data({
140             key     => $new_key,
141             value   => $value_sector,
142         });
143         $value_sector->increment_refcount;
144     }
145     else {
146         $sector->write_data({
147             key     => $new_key,
148             value   => $value_sector->clone,
149         });
150     }
151
152     return;
153 }
154
155 # exists returns '', not undefined.
156 sub key_exists {
157     my $self = shift;
158     my ($obj, $key) = @_;
159
160     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
161         or return '';
162
163 #    if ( $sector->staleness != $obj->_staleness ) {
164 #        return '';
165 #    }
166
167     my $data = $sector->get_data_for({
168 #        key_md5    => $self->_apply_digest( $key ),
169         key        => $key,
170         allow_head => 1,
171     });
172
173     # exists() returns 1 or '' for true/false.
174     return $data ? 1 : '';
175 }
176
177 sub delete_key {
178     my $self = shift;
179     my ($obj, $key) = @_;
180
181     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
182         or return '';
183
184 #    if ( $sector->staleness != $obj->_staleness ) {
185 #        return '';
186 #    }
187
188     return $sector->delete_key({
189 #        key_md5    => $self->_apply_digest( $key ),
190         key        => $key,
191         allow_head => 0,
192     });
193 }
194
195 sub write_value {
196     my $self = shift;
197     my ($obj, $key, $value) = @_;
198
199     my $r = Scalar::Util::reftype( $value ) || '';
200     {
201         last if $r eq '';
202         last if $r eq 'HASH';
203         last if $r eq 'ARRAY';
204
205         DBM::Deep->_throw_error(
206             "Storage of references of type '$r' is not supported."
207         );
208     }
209
210     # Load the reference entry
211     # Determine if the row was deleted under us
212     # 
213
214     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
215         or die "Cannot load sector at '@{[$obj->_base_offset]}'\n";;
216
217     my ($type, $class);
218     if ( $r eq 'ARRAY' || $r eq 'HASH' ) {
219         my $tmpvar;
220         if ( $r eq 'ARRAY' ) {
221             $tmpvar = tied @$value;
222         } elsif ( $r eq 'HASH' ) {
223             $tmpvar = tied %$value;
224         }
225
226         if ( $tmpvar ) {
227             my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
228
229             unless ( $is_dbm_deep ) {
230                 DBM::Deep->_throw_error( "Cannot store something that is tied." );
231             }
232
233             unless ( $tmpvar->_engine->storage == $self->storage ) {
234                 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
235             }
236
237             # Load $tmpvar's sector
238
239             # First, verify if we're storing the same thing to this spot. If we
240             # are, then this should be a no-op. -EJS, 2008-05-19
241             
242             # See whether or not we are storing ourselves to ourself.
243             # Write the sector as data in this reference (keyed by $key)
244             my $value_sector = $self->load_sector( $tmpvar->_base_offset, 'refs' );
245             $sector->write_data({
246                 key     => $key,
247 #                key_md5 => $self->_apply_digest( $key ),
248                 value   => $value_sector,
249             });
250             $value_sector->increment_refcount;
251
252             return 1;
253         }
254
255         $type = substr( $r, 0, 1 );
256         $class = 'DBM::Deep::Sector::DBI::Reference';
257     }
258     else {
259         if ( tied($value) ) {
260             DBM::Deep->_throw_error( "Cannot store something that is tied." );
261         }
262
263         $class = 'DBM::Deep::Sector::DBI::Scalar';
264         $type  = 'S';
265     }
266
267     # Create this after loading the reference sector in case something bad
268     # happens. This way, we won't allocate value sector(s) needlessly.
269     my $value_sector = $class->new({
270         engine => $self,
271         data   => $value,
272         type   => $type,
273     });
274
275     $sector->write_data({
276         key     => $key,
277 #        key_md5 => $self->_apply_digest( $key ),
278         value   => $value_sector,
279     });
280
281     $self->_descend( $value, $value_sector );
282
283     return 1;
284 }
285
286 sub begin_work {
287     my $self = shift;
288     die "Transactions are not supported by this engine"
289         unless $self->supports('transactions');
290
291     if ( $self->in_txn ) {
292         DBM::Deep->_throw_error( "Cannot begin_work within an active transaction" );
293     }
294
295     $self->storage->begin_work;
296
297     $self->in_txn( 1 );
298
299     return 1;
300
301
302 sub rollback {
303     my $self = shift;
304     die "Transactions are not supported by this engine"
305         unless $self->supports('transactions');
306
307     if ( !$self->in_txn ) {
308         DBM::Deep->_throw_error( "Cannot rollback without an active transaction" );
309     }
310
311     $self->storage->rollback;
312
313     $self->in_txn( 0 );
314
315     return 1;
316
317
318 sub commit {
319     my $self = shift;
320     die "Transactions are not supported by this engine"
321         unless $self->supports('transactions');
322
323     if ( !$self->in_txn ) {
324         DBM::Deep->_throw_error( "Cannot commit without an active transaction" );
325     }
326
327     $self->storage->commit;
328
329     $self->in_txn( 0 );
330
331     return 1;
332 }
333
334 sub in_txn {
335     my $self = shift;
336     $self->{in_txn} = shift if @_;
337     $self->{in_txn};
338 }
339
340 sub supports {
341     my $self = shift;
342     my ($feature) = @_;
343
344     if ( $feature eq 'transactions' ) {
345 #        return 1 if $self->storage->driver eq 'sqlite';
346         return;
347     }
348     return;
349 }
350
351 1;
352 __END__