Got arrays working, requiring that make_reference and clone be added and functional
[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         });
88
89         $sector->write_data({
90 #            key_md5 => $key_md5,
91             key     => $key,
92             value   => $value_sector,
93         });
94     }
95
96     return $value_sector->data;
97 }
98
99 sub get_classname {
100     my $self = shift;
101     my ($obj) = @_;
102
103     return;
104 }
105
106 sub make_reference {
107     my $self = shift;
108     my ($obj, $old_key, $new_key) = @_;
109
110     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
111         or return;
112
113 #    if ( $sector->staleness != $obj->_staleness ) {
114 #        return;
115 #    }
116
117     my $value_sector = $sector->get_data_for({
118         key        => $old_key,
119         allow_head => 1,
120     });
121
122     unless ( $value_sector ) {
123         $value_sector = DBM::Deep::Sector::DBI::Scalar->new({
124             engine => $self,
125             data   => undef,
126         });
127
128         $sector->write_data({
129             key     => $old_key,
130             value   => $value_sector,
131         });
132     }
133
134     if ( $value_sector->isa( 'DBM::Deep::Sector::DBI::Reference' ) ) {
135         $sector->write_data({
136             key     => $new_key,
137             value   => $value_sector,
138         });
139         $value_sector->increment_refcount;
140     }
141     else {
142         $sector->write_data({
143             key     => $new_key,
144             value   => $value_sector->clone,
145         });
146     }
147
148     return;
149 }
150
151 # exists returns '', not undefined.
152 sub key_exists {
153     my $self = shift;
154     my ($obj, $key) = @_;
155
156     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
157         or return '';
158
159 #    if ( $sector->staleness != $obj->_staleness ) {
160 #        return '';
161 #    }
162
163     my $data = $sector->get_data_for({
164 #        key_md5    => $self->_apply_digest( $key ),
165         key        => $key,
166         allow_head => 1,
167     });
168
169     # exists() returns 1 or '' for true/false.
170     return $data ? 1 : '';
171 }
172
173 sub delete_key {
174     my $self = shift;
175     my ($obj, $key) = @_;
176
177     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
178         or return '';
179
180 #    if ( $sector->staleness != $obj->_staleness ) {
181 #        return '';
182 #    }
183
184     return $sector->delete_key({
185 #        key_md5    => $self->_apply_digest( $key ),
186         key        => $key,
187         allow_head => 0,
188     });
189 }
190
191 sub write_value {
192     my $self = shift;
193     my ($obj, $key, $value) = @_;
194
195     my $r = Scalar::Util::reftype( $value ) || '';
196     {
197         last if $r eq '';
198         last if $r eq 'HASH';
199         last if $r eq 'ARRAY';
200
201         DBM::Deep->_throw_error(
202             "Storage of references of type '$r' is not supported."
203         );
204     }
205
206     # Load the reference entry
207     # Determine if the row was deleted under us
208     # 
209
210     my $sector = $self->load_sector( $obj->_base_offset, 'refs' )
211         or die "Cannot load sector at '@{[$obj->_base_offset]}'\n";;
212
213     my ($type, $class);
214     if ( $r eq 'ARRAY' || $r eq 'HASH' ) {
215         my $tmpvar;
216         if ( $r eq 'ARRAY' ) {
217             $tmpvar = tied @$value;
218         } elsif ( $r eq 'HASH' ) {
219             $tmpvar = tied %$value;
220         }
221
222         if ( $tmpvar ) {
223             my $is_dbm_deep = eval { local $SIG{'__DIE__'}; $tmpvar->isa( 'DBM::Deep' ); };
224
225             unless ( $is_dbm_deep ) {
226                 DBM::Deep->_throw_error( "Cannot store something that is tied." );
227             }
228
229             unless ( $tmpvar->_engine->storage == $self->storage ) {
230                 DBM::Deep->_throw_error( "Cannot store values across DBM::Deep files. Please use export() instead." );
231             }
232
233             # Load $tmpvar's sector
234
235             # First, verify if we're storing the same thing to this spot. If we
236             # are, then this should be a no-op. -EJS, 2008-05-19
237             
238             # See whether or not we are storing ourselves to ourself.
239             # Write the sector as data in this reference (keyed by $key)
240             my $value_sector = $self->load_sector( $tmpvar->_base_offset );
241             $sector->write_data({
242                 key     => $key,
243                 key_md5 => $self->_apply_digest( $key ),
244                 value   => $value_sector,
245             });
246             $value_sector->increment_refcount;
247
248             return 1;
249         }
250
251         $type = substr( $r, 0, 1 );
252         $class = 'DBM::Deep::Sector::DBI::Reference';
253     }
254     else {
255         if ( tied($value) ) {
256             DBM::Deep->_throw_error( "Cannot store something that is tied." );
257         }
258
259         $class = 'DBM::Deep::Sector::DBI::Scalar';
260         $type  = 'S';
261     }
262
263     # Create this after loading the reference sector in case something bad
264     # happens. This way, we won't allocate value sector(s) needlessly.
265     my $value_sector = $class->new({
266         engine => $self,
267         data   => $value,
268         type   => $type,
269     });
270
271     $sector->write_data({
272         key     => $key,
273 #        key_md5 => $self->_apply_digest( $key ),
274         value   => $value_sector,
275     });
276
277     # This code is to make sure we write all the values in the $value to the
278     # disk and to make sure all changes to $value after the assignment are
279     # reflected on disk. This may be counter-intuitive at first, but it is
280     # correct dwimmery.
281     #   NOTE - simply tying $value won't perform a STORE on each value. Hence,
282     # the copy to a temp value.
283     if ( $r eq 'ARRAY' ) {
284         my @temp = @$value;
285         tie @$value, 'DBM::Deep', {
286             base_offset => $value_sector->offset,
287 #            staleness   => $value_sector->staleness,
288             storage     => $self->storage,
289             engine      => $self,
290         };
291         @$value = @temp;
292         bless $value, 'DBM::Deep::Array' unless Scalar::Util::blessed( $value );
293     }
294     elsif ( $r eq 'HASH' ) {
295         my %temp = %$value;
296         tie %$value, 'DBM::Deep', {
297             base_offset => $value_sector->offset,
298 #            staleness   => $value_sector->staleness,
299             storage     => $self->storage,
300             engine      => $self,
301         };
302
303         %$value = %temp;
304         bless $value, 'DBM::Deep::Hash' unless Scalar::Util::blessed( $value );
305     }
306
307     return 1;
308 }
309
310 sub begin_work {
311     my $self = shift;
312     my ($obj) = @_;
313 }
314
315 sub rollback {
316     my $self = shift;
317     my ($obj) = @_;
318 }
319
320 sub commit {
321     my $self = shift;
322     my ($obj) = @_;
323 }
324
325
326 1;
327 __END__