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