|
@@ -90,6 +90,9 @@ class LeQuery(object):
|
90
|
90
|
##@brief Abstract class handling query with filters
|
91
|
91
|
#
|
92
|
92
|
#@todo add handling of inter-datasource queries
|
|
93
|
+#
|
|
94
|
+#@warning relationnal filters on multiple classes from different datasource
|
|
95
|
+# will generate a lot of subqueries
|
93
|
96
|
class LeFilteredQuery(LeQuery):
|
94
|
97
|
|
95
|
98
|
##@brief The available operators used in query definitions
|
|
@@ -117,16 +120,80 @@ class LeFilteredQuery(LeQuery):
|
117
|
120
|
super().__init__(target_class)
|
118
|
121
|
##@brief The query filter tuple(std_filter, relational_filters)
|
119
|
122
|
self.__query_filter = None
|
|
123
|
+ ##@brief Stores potential subqueries (used when a query implies
|
|
124
|
+ # more than one datasource.
|
|
125
|
+ #
|
|
126
|
+ # Subqueries are tuple(target_class_ref_field, LeGetQuery)
|
|
127
|
+ self.subqueries = None
|
120
|
128
|
self.set_query_filter(query_filters)
|
121
|
129
|
|
122
|
130
|
##@brief Add filter(s) to the query
|
|
131
|
+ #
|
|
132
|
+ # This method is also able to slice query if different datasources are
|
|
133
|
+ # implied in the request
|
|
134
|
+ #
|
123
|
135
|
#@param query_filter list|tuple|str : A single filter or a list of filters
|
124
|
136
|
#@see LeFilteredQuery._prepare_filters()
|
125
|
137
|
def set_query_filter(self, query_filter):
|
126
|
138
|
if isinstance(query_filter, str):
|
127
|
139
|
query_filter = [query_filter]
|
128
|
|
- self.__query_filter = self._prepare_filters(query_filter)
|
129
|
|
-
|
|
140
|
+ #Query filter prepration
|
|
141
|
+ filters_orig , rel_filters = self._prepare_filters(query_filter)
|
|
142
|
+ # Here we now that each relational filter concern only one datasource
|
|
143
|
+ # thank's to _prepare_relational_fields
|
|
144
|
+
|
|
145
|
+ #Multiple datasources detection
|
|
146
|
+ self_ds_name = self._target_class._datasource_name
|
|
147
|
+ result_rel_filters = list() # The filters that will stay in the query
|
|
148
|
+ other_ds_filters = dict()
|
|
149
|
+ for rfilter in rel_filters:
|
|
150
|
+ (rfield, ref_dict), op, value = rfilter
|
|
151
|
+ #rfield : the field in self._target_class
|
|
152
|
+ tmp_rel_filter = dict() #designed to stores rel_field of same DS
|
|
153
|
+ # First step : simplification
|
|
154
|
+ # Trying to delete relational filters done on referenced class uid
|
|
155
|
+ for tclass, tfield in ref_dict.items():
|
|
156
|
+ #tclass : reference target class
|
|
157
|
+ #tfield : referenced field from target class
|
|
158
|
+ if tfield == tclass.uid_fieldname:
|
|
159
|
+ #This relational filter can be simplified as
|
|
160
|
+ # ref_field, op, value
|
|
161
|
+ # Note : we will have to dedup filters_orig
|
|
162
|
+ filters_orig.append((rfield, op, value))
|
|
163
|
+ del(ref_dict[tclass])
|
|
164
|
+ #Determine what to do with other relational filters given
|
|
165
|
+ # referenced class datasource
|
|
166
|
+ #Remember : each class in a relational filter has the same
|
|
167
|
+ # datasource
|
|
168
|
+ tclass = list(ref_dict.keys())[0]
|
|
169
|
+ cur_ds = tclass._datasource_name
|
|
170
|
+ if cur_ds == self_ds_name:
|
|
171
|
+ # Same datasource, the filter stay is self query
|
|
172
|
+ result_rel_filters.append(((rfield, ref_dict), op, value))
|
|
173
|
+ else:
|
|
174
|
+ # Different datasource, we will have to create a subquery
|
|
175
|
+ if cur_ds not in other_ds_filters:
|
|
176
|
+ other_ds_filters[cur_ds] = list()
|
|
177
|
+ other_ds_filters[cur_ds].append(
|
|
178
|
+ ((rfield, ref_dict), op, value))
|
|
179
|
+ #deduplication of std filters
|
|
180
|
+ filters_orig = list(set(filters_orig))
|
|
181
|
+ # Sets __query_filter attribute of self query
|
|
182
|
+ self.__query_filter = (filters_orig, result_rel_filters)
|
|
183
|
+
|
|
184
|
+ #Sub queries creation
|
|
185
|
+ subq = list()
|
|
186
|
+ for ds, rfilters in other_ds_filters.items():
|
|
187
|
+ for rfilter in rfilters:
|
|
188
|
+ (rfield, ref_dict), op, value = rfilter
|
|
189
|
+ for tclass, tfield in ref_dict.items():
|
|
190
|
+ query = LeGetQuery(
|
|
191
|
+ target_class = tclass,
|
|
192
|
+ query_filter = [(rfield, op, value)],
|
|
193
|
+ field_list = [tfield])
|
|
194
|
+ subq.append((rfield, query))
|
|
195
|
+
|
|
196
|
+ ##@return informations
|
130
|
197
|
def dump_infos(self):
|
131
|
198
|
ret = super().dump_infos()
|
132
|
199
|
ret['query_filter'] = self.__query_filter
|
|
@@ -243,34 +310,6 @@ field to use for the relational filter"
|
243
|
310
|
"Error while preparing filters : ",
|
244
|
311
|
err_l)
|
245
|
312
|
return (res_filters, rel_filters)
|
246
|
|
-
|
247
|
|
- ##@brief Prepare & check relational field
|
248
|
|
- #
|
249
|
|
- # The result is a tuple with (field, ref_field, concerned_classes), with :
|
250
|
|
- # - field the target_class field name
|
251
|
|
- # - ref_field the concerned_classes field names
|
252
|
|
- # - concerned_classes a set of concerned LeObject classes
|
253
|
|
- #@param field str : The target_class field name
|
254
|
|
- #@param ref_field str : The referenced class field name
|
255
|
|
- #@return a tuple(field, concerned_classes, ref_field) or an Exception
|
256
|
|
- # class instance
|
257
|
|
- def _prepare_relational_fields(self,field, ref_field):
|
258
|
|
- field_dh = self._target_class.field(field)
|
259
|
|
- concerned_classes = []
|
260
|
|
- linked_classes = field_dh.linked_classes
|
261
|
|
- if linked_classes is None:
|
262
|
|
- linked_classes = []
|
263
|
|
- for l_class in linked_classes:
|
264
|
|
- try:
|
265
|
|
- l_class.field(ref_field)
|
266
|
|
- concerned_classes.append(l_class)
|
267
|
|
- except KeyError:
|
268
|
|
- pass
|
269
|
|
- if len(concerned_classes) > 0:
|
270
|
|
- return (field, ref_field, concerned_classes)
|
271
|
|
- else:
|
272
|
|
- msg = "None of the linked class of field %s has a field named '%s'"
|
273
|
|
- return ValueError(msg % (field, ref_field))
|
274
|
313
|
|
275
|
314
|
## @brief Check and split a query filter
|
276
|
315
|
# @note The query_filter format is "FIELD OPERATOR VALUE"
|
|
@@ -356,8 +395,7 @@ field to use for the relational filter"
|
356
|
395
|
#@param ref_field str|None : The referenced field name (if None use
|
357
|
396
|
#uniq identifiers as referenced field
|
358
|
397
|
#@return a well formed relational filter tuple or an Exception instance
|
359
|
|
- @classmethod
|
360
|
|
- def __prepare_relational_fields(cls, fieldname, ref_field = None):
|
|
398
|
+ def _prepare_relational_fields(self, fieldname, ref_field = None):
|
361
|
399
|
datahandler = self._target_class.field(fieldname)
|
362
|
400
|
# now we are going to fetch the referenced class to see if the
|
363
|
401
|
# reference field is valid
|
|
@@ -367,7 +405,12 @@ field to use for the relational filter"
|
367
|
405
|
for ref_class in ref_classes:
|
368
|
406
|
ref_dict[ref_class] = ref_class.uid_fieldname
|
369
|
407
|
else:
|
|
408
|
+ r_ds = None
|
370
|
409
|
for ref_class in ref_classes:
|
|
410
|
+ if r_ds is None:
|
|
411
|
+ r_ds = ref_class._datasource_name
|
|
412
|
+ elif ref_class._datasource_name != r_ds:
|
|
413
|
+ return RuntimeError("All referenced class doesn't have the same datasource. Query not possible")
|
371
|
414
|
if ref_field in ref_class.fieldnames(True):
|
372
|
415
|
ref_dict[ref_class] = ref_field
|
373
|
416
|
else:
|
|
@@ -376,7 +419,7 @@ the relational filter %s" % ref_class.__name__)
|
376
|
419
|
if len(ref_dict) == 0:
|
377
|
420
|
return NameError( "No field named '%s' in referenced objects"
|
378
|
421
|
% ref_field)
|
379
|
|
- return ( (fieldname, ref_dict), op, value)
|
|
422
|
+ return (fieldname, ref_dict)
|
380
|
423
|
|
381
|
424
|
|
382
|
425
|
##@brief A query to insert a new object
|