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,10 +294,11 @@ class Reference(DataHandler):
294 294
     #@return value
295 295
     #@todo implement the check when we have LeObject uid check value
296 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 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 302
             raise FieldValidationError("LeObject instance or id expected for a reference field")
302 303
         return value
303 304
 

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

@@ -156,9 +156,9 @@ class LeObject(object):
156 156
     @classmethod
157 157
     def reference_handlers(cls, with_backref = True):
158 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 163
     ##@brief Return a LeObject child class from a name
164 164
     # @warning This method has to be called from dynamically generated LeObjects

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

@@ -27,3 +27,38 @@ def _activate():
27 27
 ##@defgroup plugin_mongodb_datasource MongoDB datasource plugin
28 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,6 +202,9 @@ class MongoDbDatasource(object):
202 202
         #Non abstract beahavior
203 203
         mongo_filters = self.__process_filters(
204 204
             target, filters, relational_filters)
205
+        #Updating backref before deletion
206
+        self.__update_backref_filtered(target, filters, relational_filters,
207
+            None)
205 208
         res = self.__collection(target).delete_many(mongo_filters)
206 209
         return res.deleted_count
207 210
 
@@ -220,7 +223,8 @@ class MongoDbDatasource(object):
220 223
         mongo_filters = self.__process_filters(
221 224
             target, filters, relational_filters)
222 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 228
         return res['n']
225 229
 
226 230
     ## @brief Inserts a record in a given collection
@@ -229,7 +233,7 @@ class MongoDbDatasource(object):
229 233
     # @return the inserted uid
230 234
     def insert(self, target, new_datas):
231 235
         res = self.__collection(target).insert(new_datas)
232
-        target.make_consistency(datas=new_datas)
236
+        self.__update_backref(target, None, new_datas) 
233 237
         return str(res)
234 238
 
235 239
     ## @brief Inserts a list of records in a given collection
@@ -239,9 +243,23 @@ class MongoDbDatasource(object):
239 243
     def insert_multi(self, target, datas_list):
240 244
         res = self.__collection(target).insert_many(datas_list)
241 245
         for new_datas in datas_list:
246
+            self.__update_backref(target, None, new_datas) 
242 247
             target.make_consistency(datas=new_datas)
243 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 263
     ##@brief Update back references of an object
246 264
     #@ingroup plugin_mongodb_bref_op
247 265
     #
@@ -284,16 +302,18 @@ class MongoDbDatasource(object):
284 302
         #   LeoClass2: {...
285 303
         #
286 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 310
             if (oldd and newd and old_datas[fname] == new_datas[fname])\
291 311
                     or not(oldd or newd):
292 312
                 #No changes or not concerned
293 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 317
                 #fdh is a multiple ref. So the update preparation will be
298 318
                 #divided into two loops :
299 319
                 #- one loop for deleting old datas
@@ -309,12 +329,12 @@ class MongoDbDatasource(object):
309 329
                     to_add = [  val
310 330
                                 for val in new_values
311 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 334
                     to_add = []
315
-                elif not oldd and newdd:
335
+                elif not oldd and newd:
316 336
                     to_del = []
317
-                    to_add = new_datas[fname]
337
+                    to_add = [new_datas[fname]]
318 338
                 #Calling __back_ref_upd_one_value() with good arguments
319 339
                 for vtype, vlist in [('old',to_del), ('new', to_add)]:
320 340
                     for value in vlist:
@@ -402,7 +422,7 @@ class MongoDbDatasource(object):
402 422
             old_value = old_datas[fname]
403 423
             bref_cls, bref_leo, bref_dh, bref_val = self.__bref_get_check(
404 424
                 bref_cls, old_value, bref_fname)
405
-            if issubclass(bref_dh, MultipleRef):
425
+            if issubclass(bref_dh.__class__, MultipleRef):
406 426
                 #
407 427
                 # Multiple ref update (iterable)
408 428
                 if old_value not in bref_val:
@@ -451,7 +471,7 @@ value : in %s field %s" % (bref_leo, ref_fname))
451 471
         elif newd:
452 472
             #It's an "insert"
453 473
             new_value = new_datas[fname]
454
-            if issubclass(bref_dh, MultipleRef):
474
+            if issubclass(bref_dh.__class__, MultipleRef):
455 475
                 if isinstance(bref_val, set):
456 476
                     bref_val |= set([new_value])
457 477
                 else:
@@ -463,6 +483,7 @@ value : in %s field %s" % (bref_leo, ref_fname))
463 483
         
464 484
     
465 485
     ##@brief Fetch back reference informations
486
+    #@warning thank's to __update_backref_act() this method is useless
466 487
     #@param bref_cls LeObject child class : __back_reference[0]
467 488
     #@param uidv mixed : UID value (the content of the reference field)
468 489
     #@param bref_fname str : the name of the back_reference field
@@ -473,7 +494,7 @@ value : in %s field %s" % (bref_leo, ref_fname))
473 494
     #@throw LodelFatalError if the back reference field is not a Reference
474 495
     #subclass (major failure)
475 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 498
         if len(bref_leo) == 0:
478 499
             raise MongoDbConsistencyError("Unable to get the object we make \
479 500
 reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
@@ -483,34 +504,6 @@ reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
483 504
 is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
484 505
         bref_val = bref_leo.data(bref_fname)
485 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 508
     ##@brief Act on abstract LeObject child
516 509
     #
@@ -522,6 +515,7 @@ is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
522 515
     #@param act function : the caller method
523 516
     #@param **kwargs other arguments
524 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 519
     def __act_on_abstract(self,
526 520
         target, filters, relational_filters, act, **kwargs):
527 521
 

Loading…
Cancel
Save