|
@@ -11,14 +11,18 @@ from pymongo.errors import BulkWriteError
|
11
|
11
|
|
12
|
12
|
from lodel import logger
|
13
|
13
|
from lodel.leapi.leobject import CLASS_ID_FIELDNAME
|
|
14
|
+from lodel.leapi.datahandlers.base_classes import Reference, MultipleRef
|
|
15
|
+from lodel.exceptions import LodelException, LodelFatalError
|
14
|
16
|
|
15
|
17
|
from . import utils
|
|
18
|
+from .exceptions import *
|
16
|
19
|
from .utils import object_collection_name, collection_name, \
|
17
|
20
|
MONGODB_SORT_OPERATORS_MAP, connection_string, mongo_fieldname
|
18
|
21
|
|
19
|
|
-class MongoDbDataSourceError(Exception):
|
20
|
|
- pass
|
21
|
22
|
|
|
23
|
+##@brief Datasource class
|
|
24
|
+#@ingroup plugin_mongodb_datasource
|
|
25
|
+#@todo Make it inherit from an abstract datasource !
|
22
|
26
|
class MongoDbDatasource(object):
|
23
|
27
|
|
24
|
28
|
##@brief Stores existing connections
|
|
@@ -238,6 +242,276 @@ class MongoDbDatasource(object):
|
238
|
242
|
target.make_consistency(datas=new_datas)
|
239
|
243
|
return list(res.inserted_ids)
|
240
|
244
|
|
|
245
|
+ ##@brief Update back references of an object
|
|
246
|
+ #@ingroup plugin_mongodb_bref_op
|
|
247
|
+ #
|
|
248
|
+ #old_datas and new_datas arguments are set to None to indicate
|
|
249
|
+ #insertion or deletion. Calls examples :
|
|
250
|
+ #@par LeObject insert __update backref call
|
|
251
|
+ #<pre>
|
|
252
|
+ #Insert(datas):
|
|
253
|
+ # self.make_insert(datas)
|
|
254
|
+ # self.__update_backref(self.__class__, None, datas)
|
|
255
|
+ #</pre>
|
|
256
|
+ #@par LeObject delete __update backref call
|
|
257
|
+ #Delete()
|
|
258
|
+ # old_datas = self.datas()
|
|
259
|
+ # self.make_delete()
|
|
260
|
+ # self.__update_backref(self.__class__, old_datas, None)
|
|
261
|
+ #@par LeObject update __update_backref call
|
|
262
|
+ #<pre>
|
|
263
|
+ #Update(new_datas):
|
|
264
|
+ # old_datas = self.datas()
|
|
265
|
+ # self.make_udpdate(new_datas)
|
|
266
|
+ # self.__update_backref(self.__class__, old_datas, new_datas)
|
|
267
|
+ #</pre>
|
|
268
|
+ #
|
|
269
|
+ #@param target LeObject child class
|
|
270
|
+ #@param old_datas dict : datas state before update
|
|
271
|
+ #@param new_datas dict : datas state after the update process
|
|
272
|
+ #retun None
|
|
273
|
+ def __update_backref(self, target, old_datas, new_datas):
|
|
274
|
+ #upd_dict is the dict that will allow to run updates in an optimized
|
|
275
|
+ #way (or try to help doing it)
|
|
276
|
+ #
|
|
277
|
+ #It's struct looks like :
|
|
278
|
+ # { LeoCLASS : {
|
|
279
|
+ # UID1: (
|
|
280
|
+ # LeoINSTANCE,
|
|
281
|
+ # { fname1 : value, fname2: value }),
|
|
282
|
+ # UID2 (LeoINSTANCE, {fname...}),
|
|
283
|
+ # },
|
|
284
|
+ # LeoClass2: {...
|
|
285
|
+ #
|
|
286
|
+ upd_dict = {}
|
|
287
|
+ for fname, fdh in target.reference_handlers():
|
|
288
|
+ oldd = fname in old_datas
|
|
289
|
+ newd = fname in new_datas
|
|
290
|
+ if (oldd and newd and old_datas[fname] == new_datas[fname])\
|
|
291
|
+ or not(oldd or newd):
|
|
292
|
+ #No changes or not concerned
|
|
293
|
+ continue
|
|
294
|
+ bref_cls = fdh.data_handler[0]
|
|
295
|
+ bref_fname = fdh.data_handler[1]
|
|
296
|
+ if issubclass(fdh, MultipleRef):
|
|
297
|
+ #fdh is a multiple ref. So the update preparation will be
|
|
298
|
+ #divided into two loops :
|
|
299
|
+ #- one loop for deleting old datas
|
|
300
|
+ #- one loop for inserting updated datas
|
|
301
|
+ #
|
|
302
|
+ #Preparing the list of values to delete or to add
|
|
303
|
+ if newd and oldd:
|
|
304
|
+ old_values = old_datas[fname]
|
|
305
|
+ new_values = new_datas[fname]
|
|
306
|
+ to_del = [ val
|
|
307
|
+ for val in old_values
|
|
308
|
+ if val not in new_values]
|
|
309
|
+ to_add = [ val
|
|
310
|
+ for val in new_values
|
|
311
|
+ if val not in old_values]
|
|
312
|
+ elif oldd and not newdd:
|
|
313
|
+ to_del = old_datas[fname]
|
|
314
|
+ to_add = []
|
|
315
|
+ elif not oldd and newdd:
|
|
316
|
+ to_del = []
|
|
317
|
+ to_add = new_datas[fname]
|
|
318
|
+ #Calling __back_ref_upd_one_value() with good arguments
|
|
319
|
+ for vtype, vlist in [('old',to_del), ('new', to_add)]:
|
|
320
|
+ for value in vlist:
|
|
321
|
+ #fetching backref infos
|
|
322
|
+ bref_infos = self.__bref_get_check(
|
|
323
|
+ bref_cls, value, bref_fname)
|
|
324
|
+ #preparing the upd_dict
|
|
325
|
+ upd_dict = self.__update_backref_upd_dict_prepare(
|
|
326
|
+ upd_dict, bref_infos)
|
|
327
|
+ #preparing updated bref_infos
|
|
328
|
+ bref_cls, bref_leo, bref_dh, bref_value = bref_infos
|
|
329
|
+ bref_infos = tuple(bref_cls, bref_leo, bref_dh,
|
|
330
|
+ upd_dict[bref_cls][uid_val][1][bref_fname])
|
|
331
|
+ vdict = {vtype: value}
|
|
332
|
+ #fetch and store updated value
|
|
333
|
+ new_bref_val = self.__back_ref_upd_one_value(
|
|
334
|
+ fname, fdh, bref_infos, **vdict)
|
|
335
|
+ upd_dict[bref_cls][uid_val][1][bref_fname] = new_bref_val
|
|
336
|
+ else:
|
|
337
|
+ #fdh is a single ref so the process is simpler, we do not have
|
|
338
|
+ #to loop and we may do an update in only one
|
|
339
|
+ #__back_ref_upd_one_value() call by giving both old and new
|
|
340
|
+ #value
|
|
341
|
+ vdict = {}
|
|
342
|
+ if oldd:
|
|
343
|
+ vdict['old'] = new_datas[fname]
|
|
344
|
+ uid_val = vdict['old']
|
|
345
|
+ if newd:
|
|
346
|
+ vdict['new'] = new_datas[fname]
|
|
347
|
+ if not oldd:
|
|
348
|
+ uid_val = vdict['new']
|
|
349
|
+ #Fetching back ref infos
|
|
350
|
+ bref_infos = self.__bref_get_check(
|
|
351
|
+ bref_cls, uid_val, bref_fname)
|
|
352
|
+ #prepare the upd_dict
|
|
353
|
+ upd_dict = self.__update_backref_upd_dict_prepare(
|
|
354
|
+ upd_dict, bref_infos)
|
|
355
|
+ #forging update bref_infos
|
|
356
|
+ bref_cls, bref_leo, bref_dh, bref_value = bref_infos
|
|
357
|
+ bref_infos = tuple(bref_cls, bref_leo, bref_dh,
|
|
358
|
+ upd_dict[bref_cls][uid_val][1][bref_fname])
|
|
359
|
+ #fetche and store updated value
|
|
360
|
+ new_bref_val = self.__back_ref_upd_one_value(
|
|
361
|
+ fname, fdh , **vdict)
|
|
362
|
+ upd_dict[bref_cls][uid_val][1][bref_fname] = new_bref_val
|
|
363
|
+ #Now we've got our upd_dict ready.
|
|
364
|
+ #running the updates
|
|
365
|
+ for bref_cls, uid_dict in upd_dict.items():
|
|
366
|
+ for uidval, (leo, datas) in uid_dict.items():
|
|
367
|
+ leo.update(datas)
|
|
368
|
+
|
|
369
|
+ ##@brief Utility function designed to handle the upd_dict of
|
|
370
|
+ #__update_backref()
|
|
371
|
+ #
|
|
372
|
+ #@param upd_dict dict : in & out args modified by reference
|
|
373
|
+ #@param bref_infos tuple : as returned by __bref_get_check()
|
|
374
|
+ #@return the updated version of upd_dict
|
|
375
|
+ @staticmethod
|
|
376
|
+ def __update_backref_upd_dict_prepare(upd_dict,bref_infos):
|
|
377
|
+ bref_cls, bref_leo, bref_dh, bref_value = bref_infos
|
|
378
|
+ if bref_cls not in upd_dict:
|
|
379
|
+ upd_dict[bref_cls] = {}
|
|
380
|
+ if uid_val not in upd_dict[bref_cls]:
|
|
381
|
+ upd_dict[bref_cls][uid_val] = tuple(bref_leo, {})
|
|
382
|
+ if bref_fname not in upd_dict[bref_cls][uid_val]:
|
|
383
|
+ upd_dict[bref_cls][uid_val][1][bref_fname] = bref_value
|
|
384
|
+ return upd_dict
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+ ##@brief Prepare a one value back reference update
|
|
388
|
+ #@param fname str : the source Reference field name
|
|
389
|
+ #@param fdh DataHandler : the source Reference DataHandler
|
|
390
|
+ #@param bref_infos tuple : as returned by __bref_get_check() method
|
|
391
|
+ #@param old mixed : (optional **values) the old value
|
|
392
|
+ #@param new mixed : (optional **values) the new value
|
|
393
|
+ #@return the new back reference field value
|
|
394
|
+ def __back_ref_upd_one_value(self, fname, fdh, bref_infos, **values):
|
|
395
|
+ bref_cls, bref_leo, bref_dh, bref_val = bref_infos
|
|
396
|
+ oldd = 'old' in values
|
|
397
|
+ newd = 'new' in values
|
|
398
|
+ if oldd:
|
|
399
|
+ #
|
|
400
|
+ # We got an old value. It can be an update or a delete
|
|
401
|
+ #
|
|
402
|
+ old_value = old_datas[fname]
|
|
403
|
+ bref_cls, bref_leo, bref_dh, bref_val = self.__bref_get_check(
|
|
404
|
+ bref_cls, old_value, bref_fname)
|
|
405
|
+ if issubclass(bref_dh, MultipleRef):
|
|
406
|
+ #
|
|
407
|
+ # Multiple ref update (iterable)
|
|
408
|
+ if old_value not in bref_val:
|
|
409
|
+ raise MongodbConsistencyError("The value we want to \
|
|
410
|
+replace in this back reference update was not found in the back referenced \
|
|
411
|
+object : %s field %s" % (bref_leo, ref_fname))
|
|
412
|
+ if isinstance(old_value, set):
|
|
413
|
+ #Specific handling for set (values are not indexed)
|
|
414
|
+ bref_val -= set([old_value])
|
|
415
|
+ if newd:
|
|
416
|
+ # update value
|
|
417
|
+ bref_val |= set([new_datas[fname]])
|
|
418
|
+ else:
|
|
419
|
+ # Assert that we can handles all other iterable this
|
|
420
|
+ #way
|
|
421
|
+ for ki, val in bref_val:
|
|
422
|
+ if val == old_value:
|
|
423
|
+ if newd:
|
|
424
|
+ #Update
|
|
425
|
+ bref_val[ki] = new_datas[fname]
|
|
426
|
+ else:
|
|
427
|
+ #Deletion
|
|
428
|
+ del(bref_val[ki])
|
|
429
|
+ break
|
|
430
|
+ else:
|
|
431
|
+ raise LodelFatalError("This error should never be \
|
|
432
|
+raised ! We just checked that oldv is in bref_val...")
|
|
433
|
+ else:
|
|
434
|
+ #Single ref handling
|
|
435
|
+ if bref_val != old_value:
|
|
436
|
+ raise MongoDbConsistencyError("The value we wanted to \
|
|
437
|
+update do not match excpected value during aback reference singleReference \
|
|
438
|
+update : expected value was '%s' but found '%s' in field '%s' of leo %s" % (
|
|
439
|
+ old_value, bref_val, ref_fname, bref_leo))
|
|
440
|
+
|
|
441
|
+ if newd:
|
|
442
|
+ #update
|
|
443
|
+ bref_val = new_datas[fname]
|
|
444
|
+ else:
|
|
445
|
+ #delete
|
|
446
|
+ if not hasattr(bref_dh, "default"):
|
|
447
|
+ raise MongoDbConsistencyError("Unable to delete a \
|
|
448
|
+value for a back reference update. The concerned field don't have a default \
|
|
449
|
+value : in %s field %s" % (bref_leo, ref_fname))
|
|
450
|
+ bref_val = getattr(bref_dh, "default")
|
|
451
|
+ elif newd:
|
|
452
|
+ #It's an "insert"
|
|
453
|
+ new_value = new_datas[fname]
|
|
454
|
+ if issubclass(bref_dh, MultipleRef):
|
|
455
|
+ if isinstance(bref_val, set):
|
|
456
|
+ bref_val |= set([new_value])
|
|
457
|
+ else:
|
|
458
|
+ bref_val.append(new_value)
|
|
459
|
+ else:
|
|
460
|
+ bref_val = new_value
|
|
461
|
+ return bref_val
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+ ##@brief Fetch back reference informations
|
|
466
|
+ #@param bref_cls LeObject child class : __back_reference[0]
|
|
467
|
+ #@param uidv mixed : UID value (the content of the reference field)
|
|
468
|
+ #@param bref_fname str : the name of the back_reference field
|
|
469
|
+ #@return tuple(bref_class, bref_LeObect_instance, bref_datahandler,
|
|
470
|
+ #bref_value)
|
|
471
|
+ #@throw MongoDbConsistencyError when LeObject instance not found given
|
|
472
|
+ #uidv
|
|
473
|
+ #@throw LodelFatalError if the back reference field is not a Reference
|
|
474
|
+ #subclass (major failure)
|
|
475
|
+ def __bref_get_check(self, bref_cls, uidv, bref_fname):
|
|
476
|
+ bref_leo = bref_cls.get_from_uid(old_datas[fname])
|
|
477
|
+ if len(bref_leo) == 0:
|
|
478
|
+ raise MongoDbConsistencyError("Unable to get the object we make \
|
|
479
|
+reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
|
|
480
|
+ bref_dh = bref_leo.data_handler(bref_fname)
|
|
481
|
+ if not isinstance(bref_leo, Reference):
|
|
482
|
+ raise LodelFatalError("Found a back reference field that \
|
|
483
|
+is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
|
|
484
|
+ bref_val = bref_leo.data(bref_fname)
|
|
485
|
+ 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
|
+
|
241
|
515
|
##@brief Act on abstract LeObject child
|
242
|
516
|
#
|
243
|
517
|
#This method is designed to be called by insert, select and delete method
|