Browse Source

Continuing mongodb backreference implementation

Yann Weber 8 years ago
parent
commit
4e90475dbf

+ 4
- 3
lodel/leapi/datahandlers/base_classes.py View File

294
     #@return value
294
     #@return value
295
     #@todo implement the check when we have LeObject uid check value
295
     #@todo implement the check when we have LeObject uid check value
296
     def _check_data_value(self, value):
296
     def _check_data_value(self, value):
297
-        value= super()._check_data_value(value)
298
-        elt = self.__allowed_classes[0]
297
+        from lodel.leapi.leobject import LeObject
298
+        value = super()._check_data_value(value)
299
+        elt = list(self.__allowed_classes)[0]
299
         uid = elt.uid_fieldname()[0]# TODO multiple uid is broken
300
         uid = elt.uid_fieldname()[0]# TODO multiple uid is broken
300
-        if (expt is None and not (isinstance(value, LeObject)) or (value is uid)):
301
+        if not (hasattr(value, '__class__') and issubclass(value.__class__, LeObject)) or (value is uid):
301
             raise FieldValidationError("LeObject instance or id expected for a reference field")
302
             raise FieldValidationError("LeObject instance or id expected for a reference field")
302
         return value
303
         return value
303
 
304
 

+ 3
- 3
lodel/leapi/leobject.py View File

156
     @classmethod
156
     @classmethod
157
     def reference_handlers(cls, with_backref = True):
157
     def reference_handlers(cls, with_backref = True):
158
         return {    fname: fdh 
158
         return {    fname: fdh 
159
-                    for fname, fdh in cls.fields(True)
160
-                    if issubclass(fdh, Reference) and \
161
-                        (not with_backref or fdh.backreference is not None)}
159
+                    for fname, fdh in cls.fields(True).items()
160
+                    if issubclass(fdh.__class__, Reference) and \
161
+                        (not with_backref or fdh.back_reference is not None)}
162
     
162
     
163
     ##@brief Return a LeObject child class from a name
163
     ##@brief Return a LeObject child class from a name
164
     # @warning This method has to be called from dynamically generated LeObjects
164
     # @warning This method has to be called from dynamically generated LeObjects

+ 35
- 0
plugins/mongodb_datasource/__init__.py View File

27
 ##@defgroup plugin_mongodb_datasource MongoDB datasource plugin
27
 ##@defgroup plugin_mongodb_datasource MongoDB datasource plugin
28
 #@brief Doc about mongodb datasource
28
 #@brief Doc about mongodb datasource
29
 
29
 
30
+##@page plugin_mongodb_backref_complexity Reflexion on back reference complexity
31
+#@ingroup plugin_mongodb_bref_op
32
+#
33
+#Their is a huge performance issue in the way we implemented references
34
+#and back references for mongodb datasource :
35
+#
36
+#For each write action (update, delete or insert) we HAVE TO run a select
37
+#on all concerned LeObject. In fact those methods headers looks like
38
+#<pre>def write_action(target_cls, filters, [datas])</pre>
39
+#
40
+#We have no idea if all the modified objects are of the target class (they
41
+#can be of any target's child classes). So that means we have no idea of the
42
+#@ref base_classes.Reference "References" that will be modified by the action.
43
+#
44
+#Another problem is that when we run an update or a delete we have no idea
45
+#of the values that will be updated or deleted (we do not have the concerned
46
+#instances datas). As a result we cannot replace or delete the concerned
47
+#back references.
48
+#
49
+#In term of complexity the number of DB query looks like :
50
+#<pre>
51
+#With n the number of instances to modify : 
52
+#queryO(n) ~=  2n ( n * select + n * update )
53
+#</pre>
54
+#But it can go really bad, really fast if we take in consideration that
55
+#query's can be done on mixed classes or abstract classes. With :
56
+#- n : the number of LeObect child classes represented by the abstract class
57
+#- m : the number of LeObject child classes for each n
58
+#- o : the number of concerned back_reference classes for each m
59
+#
60
+#<pre>queryO(n,m,o) ~=  n + (n*m) + (n*m*o) => n + n*m select and n*m*o updates</pre>
61
+#
62
+#All of this is really sad especially as the update and the delete will be
63
+#run on LeObject instances....
64
+#

+ 37
- 43
plugins/mongodb_datasource/datasource.py View File

202
         #Non abstract beahavior
202
         #Non abstract beahavior
203
         mongo_filters = self.__process_filters(
203
         mongo_filters = self.__process_filters(
204
             target, filters, relational_filters)
204
             target, filters, relational_filters)
205
+        #Updating backref before deletion
206
+        self.__update_backref_filtered(target, filters, relational_filters,
207
+            None)
205
         res = self.__collection(target).delete_many(mongo_filters)
208
         res = self.__collection(target).delete_many(mongo_filters)
206
         return res.deleted_count
209
         return res.deleted_count
207
 
210
 
220
         mongo_filters = self.__process_filters(
223
         mongo_filters = self.__process_filters(
221
             target, filters, relational_filters)
224
             target, filters, relational_filters)
222
         res = self.__collection(target).update(mongo_filters, upd_datas)
225
         res = self.__collection(target).update(mongo_filters, upd_datas)
223
-        target.make_consistency(datas=upd_datas, type_query='update')
226
+        self.__update_backref_filtered(target, filters, relational_filters,
227
+            upd_datas)
224
         return res['n']
228
         return res['n']
225
 
229
 
226
     ## @brief Inserts a record in a given collection
230
     ## @brief Inserts a record in a given collection
229
     # @return the inserted uid
233
     # @return the inserted uid
230
     def insert(self, target, new_datas):
234
     def insert(self, target, new_datas):
231
         res = self.__collection(target).insert(new_datas)
235
         res = self.__collection(target).insert(new_datas)
232
-        target.make_consistency(datas=new_datas)
236
+        self.__update_backref(target, None, new_datas) 
233
         return str(res)
237
         return str(res)
234
 
238
 
235
     ## @brief Inserts a list of records in a given collection
239
     ## @brief Inserts a list of records in a given collection
239
     def insert_multi(self, target, datas_list):
243
     def insert_multi(self, target, datas_list):
240
         res = self.__collection(target).insert_many(datas_list)
244
         res = self.__collection(target).insert_many(datas_list)
241
         for new_datas in datas_list:
245
         for new_datas in datas_list:
246
+            self.__update_backref(target, None, new_datas) 
242
             target.make_consistency(datas=new_datas)
247
             target.make_consistency(datas=new_datas)
243
         return list(res.inserted_ids)
248
         return list(res.inserted_ids)
244
     
249
     
250
+    ##@brief Update backref giving an action
251
+    #@param target leObject child class
252
+    #@param filters
253
+    #@param relational_filters,
254
+    #@param datas None | dict : optional new datas if None mean we are deleting
255
+    #@return nothing (for the moment
256
+    def __update_backref_filtered(self, target, act,
257
+            filters, relational_filters, datas = None):
258
+        #gathering datas
259
+        old_datas_l = target.select(target, None, filters, relational_filters)
260
+        for old_datas in old_datas_l:
261
+            self.__update_backref(target, old_datas, datas)
262
+
245
     ##@brief Update back references of an object
263
     ##@brief Update back references of an object
246
     #@ingroup plugin_mongodb_bref_op
264
     #@ingroup plugin_mongodb_bref_op
247
     #
265
     #
284
         #   LeoClass2: {...
302
         #   LeoClass2: {...
285
         #
303
         #
286
         upd_dict = {}
304
         upd_dict = {}
287
-        for fname, fdh in target.reference_handlers():
288
-            oldd = fname in old_datas
289
-            newd = fname in new_datas
305
+        for fname, fdh in target.reference_handlers().items():
306
+            oldd = old_datas is not None and fname in old_datas and \
307
+                hasattr(fdh, 'default') and old_datas[fname] != fdh.default
308
+            newd = new_datas is not None and fname in new_datas and \
309
+                hasattr(fdh, 'default') and new_datas[fname] != fdh.default
290
             if (oldd and newd and old_datas[fname] == new_datas[fname])\
310
             if (oldd and newd and old_datas[fname] == new_datas[fname])\
291
                     or not(oldd or newd):
311
                     or not(oldd or newd):
292
                 #No changes or not concerned
312
                 #No changes or not concerned
293
                 continue
313
                 continue
294
-            bref_cls = fdh.data_handler[0]
295
-            bref_fname = fdh.data_handler[1]
296
-            if issubclass(fdh, MultipleRef):
314
+            bref_cls = fdh.back_reference[0]
315
+            bref_fname = fdh.back_reference[1]
316
+            if issubclass(fdh.__class__, MultipleRef):
297
                 #fdh is a multiple ref. So the update preparation will be
317
                 #fdh is a multiple ref. So the update preparation will be
298
                 #divided into two loops :
318
                 #divided into two loops :
299
                 #- one loop for deleting old datas
319
                 #- one loop for deleting old datas
309
                     to_add = [  val
329
                     to_add = [  val
310
                                 for val in new_values
330
                                 for val in new_values
311
                                 if val not in old_values]
331
                                 if val not in old_values]
312
-                elif oldd and not newdd:
313
-                    to_del = old_datas[fname]
332
+                elif oldd and not newd:
333
+                    to_del = [old_datas[fname]]
314
                     to_add = []
334
                     to_add = []
315
-                elif not oldd and newdd:
335
+                elif not oldd and newd:
316
                     to_del = []
336
                     to_del = []
317
-                    to_add = new_datas[fname]
337
+                    to_add = [new_datas[fname]]
318
                 #Calling __back_ref_upd_one_value() with good arguments
338
                 #Calling __back_ref_upd_one_value() with good arguments
319
                 for vtype, vlist in [('old',to_del), ('new', to_add)]:
339
                 for vtype, vlist in [('old',to_del), ('new', to_add)]:
320
                     for value in vlist:
340
                     for value in vlist:
402
             old_value = old_datas[fname]
422
             old_value = old_datas[fname]
403
             bref_cls, bref_leo, bref_dh, bref_val = self.__bref_get_check(
423
             bref_cls, bref_leo, bref_dh, bref_val = self.__bref_get_check(
404
                 bref_cls, old_value, bref_fname)
424
                 bref_cls, old_value, bref_fname)
405
-            if issubclass(bref_dh, MultipleRef):
425
+            if issubclass(bref_dh.__class__, MultipleRef):
406
                 #
426
                 #
407
                 # Multiple ref update (iterable)
427
                 # Multiple ref update (iterable)
408
                 if old_value not in bref_val:
428
                 if old_value not in bref_val:
451
         elif newd:
471
         elif newd:
452
             #It's an "insert"
472
             #It's an "insert"
453
             new_value = new_datas[fname]
473
             new_value = new_datas[fname]
454
-            if issubclass(bref_dh, MultipleRef):
474
+            if issubclass(bref_dh.__class__, MultipleRef):
455
                 if isinstance(bref_val, set):
475
                 if isinstance(bref_val, set):
456
                     bref_val |= set([new_value])
476
                     bref_val |= set([new_value])
457
                 else:
477
                 else:
463
         
483
         
464
     
484
     
465
     ##@brief Fetch back reference informations
485
     ##@brief Fetch back reference informations
486
+    #@warning thank's to __update_backref_act() this method is useless
466
     #@param bref_cls LeObject child class : __back_reference[0]
487
     #@param bref_cls LeObject child class : __back_reference[0]
467
     #@param uidv mixed : UID value (the content of the reference field)
488
     #@param uidv mixed : UID value (the content of the reference field)
468
     #@param bref_fname str : the name of the back_reference field
489
     #@param bref_fname str : the name of the back_reference field
473
     #@throw LodelFatalError if the back reference field is not a Reference
494
     #@throw LodelFatalError if the back reference field is not a Reference
474
     #subclass (major failure)
495
     #subclass (major failure)
475
     def __bref_get_check(self, bref_cls, uidv, bref_fname):
496
     def __bref_get_check(self, bref_cls, uidv, bref_fname):
476
-        bref_leo = bref_cls.get_from_uid(old_datas[fname])
497
+        bref_leo = bref_cls.get_from_uid(uidv)
477
         if len(bref_leo) == 0:
498
         if len(bref_leo) == 0:
478
             raise MongoDbConsistencyError("Unable to get the object we make \
499
             raise MongoDbConsistencyError("Unable to get the object we make \
479
 reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
500
 reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
483
 is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
504
 is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
484
         bref_val = bref_leo.data(bref_fname)
505
         bref_val = bref_leo.data(bref_fname)
485
         return (bref_leo.__class__, bref_leo, bref_dh, bref_val)
506
         return (bref_leo.__class__, bref_leo, bref_dh, bref_val)
486
-            
487
-
488
-        
489
-    
490
-    ##@defgroup plugin_mongodb_bref_op BackReferences operations
491
-    #@brief Contains back_reference operations
492
-    #@ingroup plugin_mongodb_datasource
493
-    #
494
-    #Back reference operations are implemented by privates functions 
495
-    # - __bref_op_update()
496
-    # - __bref_op_delete()
497
-    # - __bref_op_add()
498
-    #
499
-    #This 3 methods returns the same thing, a dict containing the field
500
-    #name to update associated with the new value of this field.
501
-    #
502
-    #This 3 methods also expect that the following tests was done :
503
-    # - the datahandler pointed by the back reference is a child class of
504
-    #base_classes.Reference
505
-    # - the value of this field contains or is the given old value (not for
506
-    #the __bref_op_add() method)
507
-    #
508
-    #All this methods asserts that we allready checks that the given LeObject
509
-    #instance concerned field (the one stored as back reference) is a child
510
-    #class of base_classes.Reference
511
-    #
512
-    #@todo factorise __bref_op_update() and __bref_op_delete() and update this
513
-    #comment
514
 
507
 
515
     ##@brief Act on abstract LeObject child
508
     ##@brief Act on abstract LeObject child
516
     #
509
     #
522
     #@param act function : the caller method
515
     #@param act function : the caller method
523
     #@param **kwargs other arguments
516
     #@param **kwargs other arguments
524
     #@return sum of results (if it's an array it will result in a concat)
517
     #@return sum of results (if it's an array it will result in a concat)
518
+    #@todo optimization implementing a cache for __bref_get_check()
525
     def __act_on_abstract(self,
519
     def __act_on_abstract(self,
526
         target, filters, relational_filters, act, **kwargs):
520
         target, filters, relational_filters, act, **kwargs):
527
 
521
 

Loading…
Cancel
Save