All tests pass except for the transaction tests under MySQL. InnoDB sucks
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep / Sector / DBI / Reference.pm
1 package DBM::Deep::Sector::DBI::Reference;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 use base 'DBM::Deep::Sector::DBI';
9
10 sub table { 'refs' }
11
12 sub _init {
13     my $self = shift;
14
15     my $e = $self->engine;
16
17     unless ( $self->offset ) {
18         my $classname = Scalar::Util::blessed( delete $self->{data} );
19         $self->{offset} = $self->engine->storage->write_to(
20             refs => undef,
21             ref_type  => $self->type,
22             classname => $classname,
23         );
24     }
25     else {
26         my ($rows) = $self->engine->storage->read_from(
27             refs => $self->offset,
28             qw( ref_type ),
29         );
30
31         $self->{type} = $rows->[0]{ref_type};
32     }
33
34     return;
35 }
36
37 sub get_data_for {
38     my $self = shift;
39     my ($args) = @_;
40
41     my ($rows) = $self->engine->storage->read_from(
42         datas => { ref_id => $self->offset, key => $args->{key} },
43         qw( id ),
44     );
45
46     return unless $rows->[0]{id};
47
48     $self->load(
49         $self->engine,
50         $rows->[0]{id},
51         'datas',
52     );
53 }
54
55 sub write_data {
56     my $self = shift;
57     my ($args) = @_;
58
59     if ( ( $args->{value}->type || 'S' ) eq 'S' ) {
60         $args->{value}{offset} = $self->engine->storage->write_to(
61             datas => $args->{value}{offset},
62             ref_id    => $self->offset,
63             data_type => 'S',
64             key       => $args->{key},
65             value     => $args->{value}{data},
66         );
67
68         $args->{value}->reload;
69     }
70     else {
71         # Write the Scalar of the Reference
72         $self->engine->storage->write_to(
73             datas => undef,
74             ref_id    => $self->offset,
75             data_type => 'R',
76             key       => $args->{key},
77             value     => $args->{value}{offset},
78         );
79     }
80 }
81
82 sub delete_key {
83     my $self = shift;
84     my ($args) = @_;
85
86     my $old_value = $self->get_data_for({
87         key => $args->{key},
88     });
89
90     my $data;
91     if ( $old_value ) {
92         $data = $old_value->data({ export => 1 });
93         $old_value->free;
94     }
95
96     return $data;
97 }
98
99 sub get_classname {
100     my $self = shift;
101     my ($rows) = $self->engine->storage->read_from(
102         'refs', $self->offset,
103         qw( classname ),
104     );
105     return unless @$rows;
106     return $rows->[0]{classname};
107 }
108
109 # Look to hoist this method into a ::Reference trait
110 sub data {
111     my $self = shift;
112     my ($args) = @_;
113     $args ||= {};
114
115     my $obj;
116     unless ( $obj = $self->engine->cache->{ $self->offset } ) {
117         $obj = DBM::Deep->new({
118             type        => $self->type,
119             base_offset => $self->offset,
120             storage     => $self->engine->storage,
121             engine      => $self->engine,
122         });
123
124         if ( $self->engine->storage->{autobless} ) {
125             my $classname = $self->get_classname;
126             if ( defined $classname ) {
127                 bless $obj, $classname;
128             }
129         }
130
131         $self->engine->cache->{$self->offset} = $obj;
132     }
133
134     # We're not exporting, so just return.
135     unless ( $args->{export} ) {
136         return $obj;
137     }
138
139     # We shouldn't export if this is still referred to.
140     if ( $self->get_refcount > 1 ) {
141         return $obj;
142     }
143
144     return $obj->export;
145 }
146
147 sub free {
148     my $self = shift;
149
150     # We're not ready to be removed yet.
151     return if $self->decrement_refcount > 0;
152
153     # Rebless the object into DBM::Deep::Null.
154     eval { %{ $self->engine->cache->{ $self->offset } } = (); };
155     eval { @{ $self->engine->cache->{ $self->offset } } = (); };
156     bless $self->engine->cache->{ $self->offset }, 'DBM::Deep::Null';
157     delete $self->engine->cache->{ $self->offset };
158
159     $self->engine->storage->delete_from(
160         'datas', { ref_id => $self->offset },
161     );
162
163     $self->engine->storage->delete_from(
164         'datas', { value => $self->offset, data_type => 'R' },
165     );
166
167     $self->SUPER::free( @_ );
168 }
169
170 sub increment_refcount {
171     my $self = shift;
172     my $refcount = $self->get_refcount;
173     $refcount++;
174     $self->write_refcount( $refcount );
175     return $refcount;
176 }
177
178 sub decrement_refcount {
179     my $self = shift;
180     my $refcount = $self->get_refcount;
181     $refcount--;
182     $self->write_refcount( $refcount );
183     return $refcount;
184 }
185
186 sub get_refcount {
187     my $self = shift;
188     my ($rows) = $self->engine->storage->read_from(
189         'refs', $self->offset,
190         qw( refcount ),
191     );
192     return $rows->[0]{refcount};
193 }
194
195 sub write_refcount {
196     my $self = shift;
197     my ($num) = @_;
198     $self->engine->storage->{dbh}->do(
199         "UPDATE refs SET refcount = ? WHERE id = ?", undef,
200         $num, $self->offset,
201     );
202 }
203
204 1;
205 __END__