1 package DBIx::Data::Collection::Set;
4 use Method::Signatures::Simple;
5 use Data::Perl::Stream::Array;
7 has _store => (is => 'ro', required => 1, init_arg => 'store');
9 has _class => (is => 'ro', predicate => '_has_class', init_arg => 'class');
11 has _set_over => (is => 'ro', required => 1, init_arg => 'set_over');
13 ## member cache (all members)
15 has _member_cache => (
16 is => 'rw', lazy_build => 1,
17 predicate => '_member_cache_built',
20 method _build__member_cache {
21 my $stream = $self->_new_raw_stream;
23 while (my ($raw) = $stream->next) {
25 if (my ($obj) = $self->_key_cache_get_raw($raw)) {
26 # can't just $self->_merge($obj, $raw) since $obj might have changed
27 $self->_refresh($obj, $raw)
29 $self->_add_to_key_cache($self->_inflate($raw))
34 $self->_notify_observers(all_members => \@cache);
38 method _add_to_member_cache ($to_add) {
39 return $to_add unless $self->_member_cache_built;
40 push @{$self->_member_cache}, $to_add;
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};
50 ## key cache - by primary/unique key
52 has _key_cache => (is => 'ro', default => sub { {} });
54 method _add_to_key_cache ($to_add) {
55 $self->_key_cache->{$self->_object_to_id($to_add)} = $to_add;
59 method _remove_from_key_cache ($to_remove) {
60 # should return $to_remove
61 delete $self->_key_cache->{$self->_object_to_id($to_remove)}
64 method _key_cache_has_raw ($raw) {
65 exists $self->_key_cache->{$self->_raw_to_id($raw)}
68 method _key_cache_has_object ($obj) {
69 exists $self->_key_cache->{$self->_object_to_id($obj)}
72 method _key_cache_get_raw ($raw) {
73 $self->_key_cache_get_id($self->_raw_to_id($raw))
76 method _key_cache_get_object ($obj) {
77 $self->_key_cache_get_id($self->_object_to_id($obj))
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))
85 method _key_cache_get_id ($id) {
86 exists $self->_key_cache->{$id}
87 ? ($self->_key_cache->{$id})
93 has _observer_callbacks => (
94 is => 'ro', isa => 'ArrayRef', default => sub { [] }
97 method _notify_observers ($event, $payload) {
98 foreach my $cb (@{$self->_observer_callbacks}) {
99 $self->$cb($event, $payload);
103 ## thunking between the store representation and the set representation
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)
110 # _deflate_spec is attributes of final repr -> raw data
112 method _inflate ($raw) {
113 bless($raw, $self->_class) if $self->_has_class;
117 method _deflate ($obj) {
121 method _merge ($obj, $raw) {
122 @{$obj}{keys %$raw} = values %$raw;
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
134 method _deflate_spec ($spec) {
138 ## methods to get ids
140 method _raw_to_id ($raw) {
141 # XXX must escape this. or do something else.
142 join ';', map $raw->{$_}, @{$self->_set_over}
145 method _object_to_id ($obj) {
146 $self->_raw_to_id($self->_deflate($obj))
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}
156 ## array-ish operations - i.e. get all members
158 method _new_raw_stream {
159 $self->_store->new_select_command([])->execute
163 @{$self->_member_cache};
167 Data::Perl::Stream::Array->new(array => $self->_member_cache);
173 if (my ($got) = $self->_key_cache_get_object_spec($spec)) {
176 if (my ($raw) = $self->_get_from_store($self->_deflate_spec($spec))) {
177 return $self->_add_to_key_cache($self->_inflate($raw))
179 return undef # we aren't handling cache misses here yet
182 method _get_from_store ($raw) {
183 $self->_store->new_select_single_command($raw)->execute
189 $self->_add_to_store($new);
190 $self->_add_to_caches($new);
191 $self->_notify_observers(add => $new);
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);
201 method _add_to_caches ($new) {
202 $self->_add_to_member_cache($new);
203 $self->_add_to_key_cache($new);
209 method remove ($old) {
210 $self->_remove_from_store($old);
211 $self->_remove_from_caches($old);
212 $self->_notify_observers(remove => $old);
216 method _remove_from_store ($old) {
217 $self->_store->new_delete_command($self->_deflate($old))->execute
220 method _remove_from_caches ($old) {
221 $self->_remove_from_member_cache($old);
222 $self->_remove_from_key_cache($old);
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