16 # You should have received a copy of the GNU Lesser General Public License along |
16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
18 """Hooks for synchronizing computed attributes""" |
18 """Hooks for synchronizing computed attributes""" |
19 |
19 |
20 |
20 |
21 from cubicweb import _ |
|
22 |
|
23 from collections import defaultdict |
21 from collections import defaultdict |
24 |
22 |
25 from rql import nodes |
23 from rql import nodes |
26 |
24 |
27 from cubicweb.server import hook |
25 from cubicweb.server import hook |
31 """Operation to recompute caches of computed attribute at commit time, |
29 """Operation to recompute caches of computed attribute at commit time, |
32 depending on what's have been modified in the transaction and avoiding to |
30 depending on what's have been modified in the transaction and avoiding to |
33 recompute twice the same attribute |
31 recompute twice the same attribute |
34 """ |
32 """ |
35 containercls = dict |
33 containercls = dict |
|
34 |
36 def add_data(self, computed_attribute, eid=None): |
35 def add_data(self, computed_attribute, eid=None): |
37 try: |
36 try: |
38 self._container[computed_attribute].add(eid) |
37 self._container[computed_attribute].add(eid) |
39 except KeyError: |
38 except KeyError: |
40 self._container[computed_attribute] = set((eid,)) |
39 self._container[computed_attribute] = set((eid,)) |
41 |
40 |
42 def precommit_event(self): |
41 def precommit_event(self): |
43 for computed_attribute_rdef, eids in self.get_data().items(): |
42 for computed_attribute_rdef, eids in self.get_data().items(): |
44 attr = computed_attribute_rdef.rtype |
43 attr = computed_attribute_rdef.rtype |
45 formula = computed_attribute_rdef.formula |
44 formula = computed_attribute_rdef.formula |
46 select = self.cnx.repo.vreg.rqlhelper.parse(formula).children[0] |
45 select = self.cnx.repo.vreg.rqlhelper.parse(formula).children[0] |
47 xvar = select.get_variable('X') |
46 xvar = select.get_variable('X') |
48 select.add_selected(xvar, index=0) |
47 select.add_selected(xvar, index=0) |
49 select.add_group_var(xvar, index=0) |
48 select.add_group_var(xvar, index=0) |
50 if None in eids: |
49 if None in eids: |
149 # entity types holding some computed attribute {etype: [computed rdefs]} |
148 # entity types holding some computed attribute {etype: [computed rdefs]} |
150 self.computed_attribute_by_etype = defaultdict(list) |
149 self.computed_attribute_by_etype = defaultdict(list) |
151 # depending entity types {dep. etype: {computed rdef: dep. etype attributes}} |
150 # depending entity types {dep. etype: {computed rdef: dep. etype attributes}} |
152 self.computed_attribute_by_etype_attrs = defaultdict(lambda: defaultdict(set)) |
151 self.computed_attribute_by_etype_attrs = defaultdict(lambda: defaultdict(set)) |
153 # depending relations def {dep. rdef: [computed rdefs] |
152 # depending relations def {dep. rdef: [computed rdefs] |
154 self.computed_attribute_by_relation = defaultdict(list) # by rdef |
153 self.computed_attribute_by_relation = defaultdict(list) # by rdef |
155 # Walk through all attributes definitions |
154 # Walk through all attributes definitions |
156 for rdef in schema.iter_computed_attributes(): |
155 for rdef in schema.iter_computed_attributes(): |
157 self.computed_attribute_by_etype[rdef.subject.type].append(rdef) |
156 self.computed_attribute_by_etype[rdef.subject.type].append(rdef) |
158 # extract the relations it depends upon - `rdef.formula_select` is |
157 # extract the relations it depends upon - `rdef.formula_select` is |
159 # expected to have been set by finalize_computed_attributes |
158 # expected to have been set by finalize_computed_attributes |
169 object_etypes = set(sol[rhs.name] for sol in select.solutions) |
168 object_etypes = set(sol[rhs.name] for sol in select.solutions) |
170 else: |
169 else: |
171 object_etypes = rschema.objects(subject_etype) |
170 object_etypes = rschema.objects(subject_etype) |
172 for object_etype in object_etypes: |
171 for object_etype in object_etypes: |
173 if rschema.final: |
172 if rschema.final: |
174 attr_for_computations = self.computed_attribute_by_etype_attrs[subject_etype] |
173 attr_for_computations = self.computed_attribute_by_etype_attrs[ |
|
174 subject_etype] |
175 attr_for_computations[rdef].add(rschema.type) |
175 attr_for_computations[rdef].add(rschema.type) |
176 else: |
176 else: |
177 depend_on_rdef = rschema.rdefs[subject_etype, object_etype] |
177 depend_on_rdef = rschema.rdefs[subject_etype, object_etype] |
178 self.computed_attribute_by_relation[depend_on_rdef].append(rdef) |
178 self.computed_attribute_by_relation[depend_on_rdef].append(rdef) |
179 |
179 |
182 regid = 'computed_attribute.%s_created' % etype |
182 regid = 'computed_attribute.%s_created' % etype |
183 selector = hook.is_instance(etype) |
183 selector = hook.is_instance(etype) |
184 yield type('%sCreatedHook' % etype, |
184 yield type('%sCreatedHook' % etype, |
185 (EntityWithCACreatedHook,), |
185 (EntityWithCACreatedHook,), |
186 {'__regid__': regid, |
186 {'__regid__': regid, |
187 '__select__': hook.Hook.__select__ & selector, |
187 '__select__': hook.Hook.__select__ & selector, |
188 'computed_attributes': computed_attributes}) |
188 'computed_attributes': computed_attributes}) |
189 |
189 |
190 def generate_relation_change_hooks(self): |
190 def generate_relation_change_hooks(self): |
191 for rdef, computed_attributes in self.computed_attribute_by_relation.items(): |
191 for rdef, computed_attributes in self.computed_attribute_by_relation.items(): |
192 regid = 'computed_attribute.%s_modified' % rdef.rtype |
192 regid = 'computed_attribute.%s_modified' % rdef.rtype |
196 optimized_computed_attributes = [] |
196 optimized_computed_attributes = [] |
197 for computed_rdef in computed_attributes: |
197 for computed_rdef in computed_attributes: |
198 optimized_computed_attributes.append( |
198 optimized_computed_attributes.append( |
199 (computed_rdef, |
199 (computed_rdef, |
200 _optimize_on(computed_rdef.formula_select, rdef.rtype)) |
200 _optimize_on(computed_rdef.formula_select, rdef.rtype)) |
201 ) |
201 ) |
202 yield type('%sModifiedHook' % rdef.rtype, |
202 yield type('%sModifiedHook' % rdef.rtype, |
203 (RelationInvolvedInCAModifiedHook,), |
203 (RelationInvolvedInCAModifiedHook,), |
204 {'__regid__': regid, |
204 {'__regid__': regid, |
205 '__select__': hook.Hook.__select__ & selector, |
205 '__select__': hook.Hook.__select__ & selector, |
206 'optimized_computed_attributes': optimized_computed_attributes}) |
206 'optimized_computed_attributes': optimized_computed_attributes}) |
207 |
207 |
208 def generate_entity_update_hooks(self): |
208 def generate_entity_update_hooks(self): |
209 for etype, attributes_computed_attributes in self.computed_attribute_by_etype_attrs.items(): |
209 for etype, attributes_computed_attributes in self.computed_attribute_by_etype_attrs.items(): |
210 regid = 'computed_attribute.%s_updated' % etype |
210 regid = 'computed_attribute.%s_updated' % etype |
211 selector = hook.is_instance(etype) |
211 selector = hook.is_instance(etype) |
212 yield type('%sModifiedHook' % etype, |
212 yield type('%sModifiedHook' % etype, |
213 (AttributeInvolvedInCAModifiedHook,), |
213 (AttributeInvolvedInCAModifiedHook,), |
214 {'__regid__': regid, |
214 {'__regid__': regid, |
215 '__select__': hook.Hook.__select__ & selector, |
215 '__select__': hook.Hook.__select__ & selector, |
216 'attributes_computed_attributes': attributes_computed_attributes}) |
216 'attributes_computed_attributes': attributes_computed_attributes}) |
217 |
217 |
218 |
218 |
219 def registration_callback(vreg): |
219 def registration_callback(vreg): |
220 vreg.register_all(globals().values(), __name__) |
220 vreg.register_all(globals().values(), __name__) |