Commit | Line | Data |
65b76960 |
1 | package DBIx::Data::Collection::Set; |
2 | |
3 | use Moose; |
4 | use Method::Signatures::Simple; |
5 | use Data::Perl::Stream::Array; |
50166086 |
6 | use Scalar::Util qw(weaken refaddr); |
65b76960 |
7 | |
8 | has _store => (is => 'ro', required => 1, init_arg => 'store'); |
9 | |
9f2b6cc8 |
10 | has _class => (is => 'ro', predicate => '_has_class', init_arg => 'class'); |
65b76960 |
11 | |
3a2e7c1c |
12 | has _set_over => (is => 'ro', required => 1, init_arg => 'set_over'); |
13 | |
14 | ## member cache (all members) |
15 | |
16 | has _member_cache => ( |
50166086 |
17 | is => 'ro', lazy_build => 1, |
3a2e7c1c |
18 | predicate => '_member_cache_built', |
50166086 |
19 | writer => '_set_member_cache', |
3a2e7c1c |
20 | ); |
65b76960 |
21 | |
22 | method _build__member_cache { |
23 | my $stream = $self->_new_raw_stream; |
24 | my @cache; |
25 | while (my ($raw) = $stream->next) { |
3a2e7c1c |
26 | my $obj = do { |
27 | if (my ($obj) = $self->_key_cache_get_raw($raw)) { |
9f2b6cc8 |
28 | # can't just $self->_merge($obj, $raw) since $obj might have changed |
29 | $self->_refresh($obj, $raw) |
3a2e7c1c |
30 | } else { |
de9534fa |
31 | $self->_add_to_key_cache($self->_inflate($raw)) |
3a2e7c1c |
32 | } |
33 | }; |
34 | push @cache, $obj; |
65b76960 |
35 | } |
9f2b6cc8 |
36 | $self->_notify_observers(all_members => \@cache); |
de9534fa |
37 | \@cache |
65b76960 |
38 | } |
39 | |
3a2e7c1c |
40 | method _add_to_member_cache ($to_add) { |
c51eabc5 |
41 | return $to_add unless $self->_member_cache_built; |
3a2e7c1c |
42 | push @{$self->_member_cache}, $to_add; |
de9534fa |
43 | $to_add |
3a2e7c1c |
44 | } |
45 | |
c51eabc5 |
46 | method _remove_from_member_cache ($to_remove) { |
47 | return $to_remove unless $self->_member_cache_built; |
48 | @{$self->_member_cache} = grep $_ ne $to_remove, @{$self->_member_cache}; |
49 | $to_remove |
50 | } |
51 | |
3a2e7c1c |
52 | ## key cache - by primary/unique key |
53 | |
54 | has _key_cache => (is => 'ro', default => sub { {} }); |
55 | |
56 | method _add_to_key_cache ($to_add) { |
57 | $self->_key_cache->{$self->_object_to_id($to_add)} = $to_add; |
de9534fa |
58 | $to_add |
3a2e7c1c |
59 | } |
60 | |
c51eabc5 |
61 | method _remove_from_key_cache ($to_remove) { |
62 | # should return $to_remove |
63 | delete $self->_key_cache->{$self->_object_to_id($to_remove)} |
64 | } |
65 | |
3a2e7c1c |
66 | method _key_cache_has_raw ($raw) { |
67 | exists $self->_key_cache->{$self->_raw_to_id($raw)} |
68 | } |
69 | |
70 | method _key_cache_has_object ($obj) { |
71 | exists $self->_key_cache->{$self->_object_to_id($obj)} |
72 | } |
73 | |
74 | method _key_cache_get_raw ($raw) { |
e49bd861 |
75 | $self->_key_cache_get_id($self->_raw_to_id($raw)) |
3a2e7c1c |
76 | } |
77 | |
78 | method _key_cache_get_object ($obj) { |
e49bd861 |
79 | $self->_key_cache_get_id($self->_object_to_id($obj)) |
3a2e7c1c |
80 | } |
81 | |
e49bd861 |
82 | method _key_cache_get_object_spec ($spec) { |
83 | # see _object_spec_to_id for doc of what the difference is |
84 | $self->_key_cache_get_id($self->_object_spec_to_id($spec)) |
85 | } |
3a2e7c1c |
86 | |
e49bd861 |
87 | method _key_cache_get_id ($id) { |
88 | exists $self->_key_cache->{$id} |
89 | ? ($self->_key_cache->{$id}) |
90 | : () |
65b76960 |
91 | } |
92 | |
50166086 |
93 | method _all_key_cache_members { |
94 | values %{$self->_key_cache} |
95 | } |
96 | |
9f2b6cc8 |
97 | ## observers |
98 | |
99 | has _observer_callbacks => ( |
50166086 |
100 | is => 'ro', default => sub { {} }, |
9f2b6cc8 |
101 | ); |
102 | |
103 | method _notify_observers ($event, $payload) { |
50166086 |
104 | my $oc = $self->_observer_callbacks; |
105 | foreach my $refaddr (keys %$oc) { |
106 | my ($obj, $cb) = @{$oc->{$refaddr}}; |
107 | unless (defined $obj) { # weak ref was garbage collected |
108 | delete $oc->{$refaddr}; |
109 | next; |
110 | } |
111 | $obj->$cb($self, $event, $payload); |
9f2b6cc8 |
112 | } |
50166086 |
113 | $payload |
114 | } |
115 | |
116 | method _register_observer ($obj, $cb) { |
117 | my $entry = [ $obj, $cb ]; |
118 | weaken($entry->[0]); |
119 | $self->_observer_callbacks->{refaddr($obj)} = $entry; |
120 | return |
121 | } |
122 | |
123 | method _setup_observation_of ($other) { |
124 | $other->_register_observer($self, method ($from, $event, $payload) { |
125 | if ($event eq 'add' or $event eq 'get') { |
126 | $self->_add_to_caches($payload); |
127 | } elsif ($event eq 'remove') { |
128 | $self->_remove_from_caches($payload); |
129 | } elsif ($event eq 'all_members') { |
130 | # separate arrayref since future add will trigger push() |
131 | $self->_set_member_cache([ @$payload ]); |
132 | } |
133 | }); |
134 | return |
9f2b6cc8 |
135 | } |
136 | |
3a2e7c1c |
137 | ## thunking between the store representation and the set representation |
138 | # |
139 | # _inflate is raw data -> final repr |
140 | # _deflate is final repr -> raw data |
141 | # _merge takes final repr + raw data and updates the repr |
142 | # (this is used for pk-generated values and later lazy loading) |
e49bd861 |
143 | # |
144 | # _deflate_spec is attributes of final repr -> raw data |
3a2e7c1c |
145 | |
65b76960 |
146 | method _inflate ($raw) { |
3347c67e |
147 | bless($raw, $self->_class) if $self->_has_class; |
c51eabc5 |
148 | $raw |
65b76960 |
149 | } |
150 | |
3a2e7c1c |
151 | method _deflate ($obj) { |
152 | +{ %$obj } |
153 | } |
154 | |
155 | method _merge ($obj, $raw) { |
156 | @{$obj}{keys %$raw} = values %$raw; |
c51eabc5 |
157 | $obj |
3a2e7c1c |
158 | } |
159 | |
9f2b6cc8 |
160 | method _refresh ($obj, $raw) { |
161 | # if $obj has been changed but not flushed we'd destroy data doing |
162 | # a blind merge - but if $obj has change tracking of some sort then |
163 | # we -could- do something safely, so this method exists to be mangled |
164 | # by subclasses |
165 | $obj |
166 | } |
167 | |
e49bd861 |
168 | method _deflate_spec ($spec) { |
169 | $spec |
170 | } |
171 | |
3a2e7c1c |
172 | ## methods to get ids |
173 | |
174 | method _raw_to_id ($raw) { |
175 | # XXX must escape this. or do something else. |
176 | join ';', map $raw->{$_}, @{$self->_set_over} |
177 | } |
178 | |
179 | method _object_to_id ($obj) { |
c51eabc5 |
180 | $self->_raw_to_id($self->_deflate($obj)) |
3a2e7c1c |
181 | } |
182 | |
e49bd861 |
183 | method _object_spec_to_id ($spec) { |
184 | # intentionally C&P from _raw_to - this is not the same thing. If a column |
185 | # were mapped to an attribute of a different name, the raw would have the |
186 | # column name as a key but an object spec would have the attribute name |
187 | join ';', map $spec->{$_}, @{$self->_set_over} |
188 | } |
189 | |
c51eabc5 |
190 | ## array-ish operations - i.e. get all members |
191 | |
e49bd861 |
192 | method _new_raw_stream { |
193 | $self->_store->new_select_command([])->execute |
194 | } |
195 | |
65b76960 |
196 | method flatten { |
197 | @{$self->_member_cache}; |
198 | } |
199 | |
200 | method as_stream { |
201 | Data::Perl::Stream::Array->new(array => $self->_member_cache); |
202 | } |
203 | |
e49bd861 |
204 | ## load single row |
205 | |
206 | method get ($spec) { |
207 | if (my ($got) = $self->_key_cache_get_object_spec($spec)) { |
208 | return $got |
209 | } |
210 | if (my ($raw) = $self->_get_from_store($self->_deflate_spec($spec))) { |
50166086 |
211 | return $self->_notify_observers( |
212 | get => $self->_add_to_key_cache($self->_inflate($raw)) |
213 | ); |
e49bd861 |
214 | } |
215 | return undef # we aren't handling cache misses here yet |
216 | } |
217 | |
218 | method _get_from_store ($raw) { |
219 | $self->_store->new_select_single_command($raw)->execute |
220 | } |
221 | |
c51eabc5 |
222 | ## add to set |
223 | |
3a2e7c1c |
224 | method add ($new) { |
225 | $self->_add_to_store($new); |
226 | $self->_add_to_caches($new); |
9f2b6cc8 |
227 | $self->_notify_observers(add => $new); |
c51eabc5 |
228 | $new |
3a2e7c1c |
229 | } |
230 | |
231 | method _add_to_store ($new) { |
232 | my $new_raw = $self->_deflate($new); |
233 | $self->_merge($new, $self->_store->new_insert_command($new_raw)->execute); |
c51eabc5 |
234 | $new |
3a2e7c1c |
235 | } |
236 | |
237 | method _add_to_caches ($new) { |
238 | $self->_add_to_member_cache($new); |
239 | $self->_add_to_key_cache($new); |
de9534fa |
240 | $new |
3a2e7c1c |
241 | } |
242 | |
c51eabc5 |
243 | ## remove from set |
244 | |
245 | method remove ($old) { |
246 | $self->_remove_from_store($old); |
247 | $self->_remove_from_caches($old); |
9f2b6cc8 |
248 | $self->_notify_observers(remove => $old); |
c51eabc5 |
249 | $old |
250 | } |
251 | |
252 | method _remove_from_store ($old) { |
a1e15ee1 |
253 | $self->_store->new_delete_single_command($self->_deflate($old))->execute |
c51eabc5 |
254 | } |
255 | |
256 | method _remove_from_caches ($old) { |
257 | $self->_remove_from_member_cache($old); |
258 | $self->_remove_from_key_cache($old); |
259 | $old |
260 | } |
261 | |
48d91d77 |
262 | ## update |
263 | |
264 | method _update_in_store ($obj) { |
265 | # this is currently a call command but we should think about it |
266 | # being a row command so that we can have RETURNING or other |
267 | # mechanisms handle things like set-on-update datetime values |
a1e15ee1 |
268 | $self->_store->new_update_single_command($self->_deflate($obj))->execute |
48d91d77 |
269 | } |
270 | |
65b76960 |
271 | 1; |