No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

query.py 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. #-*- coding: utf-8 -*-
  2. import re
  3. import copy
  4. from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
  5. from lodel.plugin.hooks import LodelHook
  6. from lodel import logger
  7. class LeQueryError(Exception):
  8. ##@brief Instanciate a new exceptions handling multiple exceptions
  9. #@param msg str : Exception message
  10. #@param exceptions dict : A list of data check Exception with concerned
  11. # field (or stuff) as key
  12. def __init__(self, msg = "Unknow error", exceptions = None):
  13. self._msg = msg
  14. self._exceptions = dict() if exceptions is None else exceptions
  15. def __repr__(self):
  16. return self.__str__()
  17. def __str__(self):
  18. msg = self._msg
  19. if isinstance(self._exceptions, dict):
  20. for_iter = self._exceptions.items()
  21. else:
  22. for_iter = enumerate(self.__exceptions)
  23. for obj, expt in for_iter:
  24. msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
  25. expt_obj = obj,
  26. expt_name=expt.__class__.__name__,
  27. expt_msg=str(expt)
  28. )
  29. return msg
  30. class LeQuery(object):
  31. ##@brief Hookname prefix
  32. _hook_prefix = None
  33. ##@brief arguments for the LeObject.check_data_value()
  34. _data_check_args = { 'complete': False, 'allow_internal': False }
  35. ##@brief Abstract constructor
  36. # @param target_class LeObject : class of object the query is about
  37. def __init__(self, target_class):
  38. if self._hook_prefix is None:
  39. raise NotImplementedError("Abstract class")
  40. if not issubclass(target_class, LeObject):
  41. raise TypeError("target class has to be a child class of LeObject")
  42. self._target_class = target_class
  43. self._datasource = target_class._datasource
  44. ##@brief Execute a query and return the result
  45. # @param **datas
  46. # @return the query result
  47. # @see LeQuery.__query()
  48. #
  49. def execute(self, **datas):
  50. if len(datas) > 0:
  51. self._target_class.check_datas_value(
  52. datas,
  53. **self._data_check_args)
  54. self._target_class.prepare_datas() #not yet implemented
  55. if self._hook_prefix is None:
  56. raise NotImplementedError("Abstract method")
  57. LodelHook.call_hook( self._hook_prefix+'_pre',
  58. self._target_class,
  59. datas)
  60. ret = self.__query(target = self._target_class._datasource, **datas)
  61. ret = LodelHook.call_hook( self._hook_prefix+'_post',
  62. self._target_class,
  63. ret)
  64. return ret
  65. ##@brief Childs classes implements this method to execute the query
  66. # @param **datas
  67. # @return query result
  68. def __query(self, **datas):
  69. raise NotImplementedError("Asbtract method")
  70. ##@return a dict with query infos
  71. def dump_infos(self):
  72. return {'target_class': self._target_class}
  73. def __repr__(self):
  74. ret = "<{classname} target={target_class}>"
  75. return ret.format(
  76. classname=self.__class__.__name__,
  77. target_class = self._target_class)
  78. ##@brief Abstract class handling query with filters
  79. #
  80. #@todo add handling of inter-datasource queries
  81. #
  82. #@warning relationnal filters on multiple classes from different datasource
  83. # will generate a lot of subqueries
  84. class LeFilteredQuery(LeQuery):
  85. ##@brief The available operators used in query definitions
  86. _query_operators = [
  87. ' = ',
  88. ' <= ',
  89. ' >= ',
  90. ' != ',
  91. ' < ',
  92. ' > ',
  93. ' in ',
  94. ' not in ',
  95. ' like ',
  96. ' not like ']
  97. ##@brief Regular expression to process filters
  98. _query_re = None
  99. ##@brief Abtract constructor for queries with filter
  100. #@param target_class LeObject : class of object the query is about
  101. #@param query_filters list : with a tuple (only one filter) or a list of
  102. # tuple or a dict: {OP,list(filters)} with OP = 'OR' or 'AND for tuple
  103. # (FIELD,OPERATOR,VALUE)
  104. def __init__(self, target_class, query_filters = None):
  105. super().__init__(target_class)
  106. ##@brief The query filter tuple(std_filter, relational_filters)
  107. self.__query_filter = None
  108. ##@brief Stores potential subqueries (used when a query implies
  109. # more than one datasource.
  110. #
  111. # Subqueries are tuple(target_class_ref_field, LeGetQuery)
  112. self.subqueries = None
  113. self.set_query_filter(query_filters)
  114. ##@brief Abstract FilteredQuery execution method
  115. #
  116. # This method takes care to execute subqueries before calling super execute
  117. def execute(self, datas = None):
  118. #copy originals filters
  119. orig_filters = copy.copy(self.__query_filter)
  120. std_filters, rel_filters = self.__query_filter
  121. for rfield, subq in self.subqueries:
  122. subq_res = subq.execute()
  123. std_filters.append(
  124. (rfield, ' in ', subq_res))
  125. self.__query_filter = (std_filters, rel_filters)
  126. try:
  127. filters, rel_filters = self.__query_filter
  128. res = super().execute(filters = filters, rel_filters = rel_filters)
  129. except Exception as e:
  130. #restoring filters even if an exception is raised
  131. self.__query_filter = orig_filter
  132. raise e #reraise
  133. #restoring filters
  134. self.__query_filter = orig_filters
  135. return res
  136. ##@brief Add filter(s) to the query
  137. #
  138. # This method is also able to slice query if different datasources are
  139. # implied in the request
  140. #
  141. #@param query_filter list|tuple|str : A single filter or a list of filters
  142. #@see LeFilteredQuery._prepare_filters()
  143. #@warning Does not support multiple UID
  144. def set_query_filter(self, query_filter):
  145. if isinstance(query_filter, str):
  146. query_filter = [query_filter]
  147. #Query filter prepration
  148. filters_orig , rel_filters = self._prepare_filters(query_filter)
  149. # Here we now that each relational filter concern only one datasource
  150. # thank's to _prepare_relational_fields
  151. #Multiple datasources detection
  152. self_ds_name = self._target_class._datasource_name
  153. result_rel_filters = list() # The filters that will stay in the query
  154. other_ds_filters = dict()
  155. for rfilter in rel_filters:
  156. (rfield, ref_dict), op, value = rfilter
  157. #rfield : the field in self._target_class
  158. tmp_rel_filter = dict() #designed to stores rel_field of same DS
  159. # First step : simplification
  160. # Trying to delete relational filters done on referenced class uid
  161. for tclass, tfield in copy.copy(ref_dict).items():
  162. #tclass : reference target class
  163. #tfield : referenced field from target class
  164. #
  165. # !!!WARNING!!!
  166. # The line below brake multi UID support
  167. #
  168. if tfield == tclass.uid_fieldname()[0]:
  169. #This relational filter can be simplified as
  170. # ref_field, op, value
  171. # Note : we will have to dedup filters_orig
  172. filters_orig.append((rfield, op, value))
  173. del(ref_dict[tclass])
  174. if len(ref_dict) == 0:
  175. continue
  176. #Determine what to do with other relational filters given
  177. # referenced class datasource
  178. #Remember : each class in a relational filter has the same
  179. # datasource
  180. tclass = list(ref_dict.keys())[0]
  181. cur_ds = tclass._datasource_name
  182. if cur_ds == self_ds_name:
  183. # Same datasource, the filter stay is self query
  184. result_rel_filters.append(((rfield, ref_dict), op, value))
  185. else:
  186. # Different datasource, we will have to create a subquery
  187. if cur_ds not in other_ds_filters:
  188. other_ds_filters[cur_ds] = list()
  189. other_ds_filters[cur_ds].append(
  190. ((rfield, ref_dict), op, value))
  191. #deduplication of std filters
  192. filters_orig = list(set(filters_orig))
  193. # Sets __query_filter attribute of self query
  194. self.__query_filter = (filters_orig, result_rel_filters)
  195. #Sub queries creation
  196. subq = list()
  197. for ds, rfilters in other_ds_filters.items():
  198. for rfilter in rfilters:
  199. (rfield, ref_dict), op, value = rfilter
  200. for tclass, tfield in ref_dict.items():
  201. query = LeGetQuery(
  202. target_class = tclass,
  203. query_filter = [(tfield, op, value)],
  204. field_list = [tfield])
  205. subq.append((rfield, query))
  206. self.subqueries = subq
  207. ##@return informations
  208. def dump_infos(self):
  209. ret = super().dump_infos()
  210. ret['query_filter'] = self.__query_filter
  211. ret['subqueries'] = self.subqueries
  212. return ret
  213. def __repr__(self):
  214. res = "<{classname} target={target_class} query_filter={query_filter}"
  215. res = ret.format(
  216. classname=self.__class__.__name__,
  217. query_filter = self.__query_filter,
  218. target_class = self._target_class)
  219. if len(self.subqueries) > 0:
  220. for n,subq in enumerate(self.subqueries):
  221. res += "\n\tSubquerie %d : %s"
  222. res %= (n, subq)
  223. res += '>'
  224. return res
  225. ## @brief Prepare filters for datasource
  226. #
  227. #A filter can be a string or a tuple with len = 3.
  228. #
  229. #This method divide filters in two categories :
  230. #
  231. #@par Simple filters
  232. #
  233. #Those filters concerns fields that represent object values (a title,
  234. #the content, etc.) They are composed of three elements : FIELDNAME OP
  235. # VALUE . Where :
  236. #- FIELDNAME is the name of the field
  237. #- OP is one of the authorized comparison operands ( see
  238. #@ref LeFilteredQuery.query_operators )
  239. #- VALUE is... a value
  240. #
  241. #@par Relational filters
  242. #
  243. #Those filters concerns on reference fields ( see the corresponding
  244. #abstract datahandler @ref lodel.leapi.datahandlers.base_classes.Reference)
  245. #The filter as quite the same composition than simple filters :
  246. # FIELDNAME[.REF_FIELD] OP VALUE . Where :
  247. #- FIELDNAME is the name of the reference field
  248. #- REF_FIELD is an optionnal addon to the base field. It indicate on wich
  249. #field of the referenced object the comparison as to be done. If no
  250. #REF_FIELD is indicated the comparison will be done on identifier.
  251. #
  252. #@param cls
  253. #@param filters_l list : This list of str or tuple (or both)
  254. #@return a tuple(FILTERS, RELATIONNAL_FILTERS
  255. #@todo move this doc in another place (a dedicated page ?)
  256. #@warning Does not supports multiple UID for an EmClass
  257. def _prepare_filters(self, filters_l):
  258. filters = list()
  259. res_filters = list()
  260. rel_filters = list()
  261. err_l = dict()
  262. #Splitting in tuple if necessary
  263. for i,fil in enumerate(filters_l):
  264. if len(fil) == 3 and not isinstance(fil, str):
  265. filters.append(tuple(fil))
  266. else:
  267. try:
  268. filters.append(self.split_filter(fil))
  269. except ValueError as e:
  270. err_l["filter %d" % i] = e
  271. for field, operator, value in filters:
  272. err_key = "%s %s %s" % (field, operator, value) #to push in err_l
  273. # Spliting field name to be able to detect a relational field
  274. field_spl = field.split('.')
  275. if len(field_spl) == 2:
  276. field, ref_field = field_spl
  277. elif len(field_spl) == 1:
  278. ref_field = None
  279. else:
  280. err_l[field] = NameError( "'%s' is not a valid relational \
  281. field name" % fieldname)
  282. continue
  283. # Checking field against target_class
  284. ret = self._check_field(self._target_class, field)
  285. if isinstance(ret, Exception):
  286. err_l[field] = ret
  287. continue
  288. field_datahandler = self._target_class.field(field)
  289. if ref_field is not None and not field_datahandler.is_reference():
  290. # inconsistency
  291. err_l[field] = NameError( "The field '%s' in %s is not \
  292. a relational field, but %s.%s was present in the filter"
  293. % ( field,
  294. self._target_class.__name__,
  295. field,
  296. ref_field))
  297. if field_datahandler.is_reference():
  298. #Relationnal field
  299. if ref_field is None:
  300. # ref_field default value
  301. #
  302. # !!! WARNING !!!
  303. # This piece of code does not supports multiple UID for an
  304. # emclass
  305. #
  306. ref_uid = [
  307. lc._uid[0] for lc in field_datahandler.linked_classes]
  308. if len(set(ref_uid)) == 1:
  309. ref_field = ref_uid[0]
  310. else:
  311. if len(ref_uid) > 1:
  312. msg = "The referenced classes are identified by \
  313. fields with different name. Unable to determine wich field to use for the \
  314. reference"
  315. else:
  316. msg = "Unknow error when trying to determine wich \
  317. field to use for the relational filter"
  318. err_l[err_key] = RuntimeError(msg)
  319. continue
  320. # Prepare relational field
  321. ret = self._prepare_relational_fields(field, ref_field)
  322. if isinstance(ret, Exception):
  323. err_l[err_key] = ret
  324. continue
  325. else:
  326. rel_filters.append((ret, operator, value))
  327. else:
  328. res_filters.append((field,operator, value))
  329. if len(err_l) > 0:
  330. raise LeApiDataCheckError(
  331. "Error while preparing filters : ",
  332. err_l)
  333. return (res_filters, rel_filters)
  334. ## @brief Check and split a query filter
  335. # @note The query_filter format is "FIELD OPERATOR VALUE"
  336. # @param query_filter str : A query_filter string
  337. # @param cls
  338. # @return a tuple (FIELD, OPERATOR, VALUE)
  339. @classmethod
  340. def split_filter(cls, query_filter):
  341. if cls._query_re is None:
  342. cls.__compile_query_re()
  343. matches = cls._query_re.match(query_filter)
  344. if not matches:
  345. msg = "The query_filter '%s' seems to be invalid"
  346. raise ValueError(msg % query_filter)
  347. result = (
  348. matches.group('field'),
  349. re.sub(r'\s', ' ', matches.group('operator'), count=0),
  350. matches.group('value').strip())
  351. result = [r.strip() for r in result]
  352. for r in result:
  353. if len(r) == 0:
  354. msg = "The query_filter '%s' seems to be invalid"
  355. raise ValueError(msg % query_filter)
  356. return result
  357. ## @brief Compile the regex for query_filter processing
  358. # @note Set _LeObject._query_re
  359. @classmethod
  360. def __compile_query_re(cls):
  361. op_re_piece = '(?P<operator>(%s)'
  362. op_re_piece %= cls._query_operators[0].replace(' ', '\s')
  363. for operator in cls._query_operators[1:]:
  364. op_re_piece += '|(%s)'%operator.replace(' ', '\s')
  365. op_re_piece += ')'
  366. re_full = '^\s*(?P<field>([a-z_][a-z0-9\-_]*\.)?[a-z_][a-z0-9\-_]*)\s*'
  367. re_full += op_re_piece+'\s*(?P<value>.*)\s*$'
  368. cls._query_re = re.compile(re_full, flags=re.IGNORECASE)
  369. pass
  370. @classmethod
  371. def _check_field(cls, target_class, fieldname):
  372. try:
  373. target_class.field(fieldname)
  374. except NameError as e:
  375. msg = "No field named '%s' in %s'"
  376. msg %= (fieldname, target_class.__name__)
  377. return NameError(msg)
  378. ##@brief Prepare a relational filter
  379. #
  380. #Relational filters are composed of a tuple like the simple filters
  381. #but the first element of this tuple is a tuple to :
  382. #
  383. #<code>( (FIELDNAME, {REF_CLASS: REF_FIELD}), OP, VALUE)</code>
  384. # Where :
  385. #- FIELDNAME is the field name is the target class
  386. #- the second element is a dict with :
  387. # - REF_CLASS as key. It's a LeObject child class
  388. # - REF_FIELD as value. The name of the referenced field in the REF_CLASS
  389. #
  390. #Visibly the REF_FIELD value of the dict will vary only when
  391. #no REF_FIELD is explicitly given in the filter string notation
  392. #and REF_CLASSES has differents uid
  393. #
  394. #@par String notation examples
  395. #<pre>contributeur IN (1,2,3,5)</pre> will be transformed into :
  396. #<pre>(
  397. # (
  398. # contributeur,
  399. # {
  400. # auteur: 'lodel_id',
  401. # traducteur: 'lodel_id'
  402. # }
  403. # ),
  404. # ' IN ',
  405. # [ 1,2,3,5 ])</pre>
  406. #@todo move the documentation to another place
  407. #
  408. #@param fieldname str : The relational field name
  409. #@param ref_field str|None : The referenced field name (if None use
  410. #uniq identifiers as referenced field
  411. #@return a well formed relational filter tuple or an Exception instance
  412. def _prepare_relational_fields(self, fieldname, ref_field = None):
  413. datahandler = self._target_class.field(fieldname)
  414. # now we are going to fetch the referenced class to see if the
  415. # reference field is valid
  416. ref_classes = datahandler.linked_classes
  417. ref_dict = dict()
  418. if ref_field is None:
  419. for ref_class in ref_classes:
  420. ref_dict[ref_class] = ref_class.uid_fieldname
  421. else:
  422. r_ds = None
  423. for ref_class in ref_classes:
  424. if r_ds is None:
  425. r_ds = ref_class._datasource_name
  426. elif ref_class._datasource_name != r_ds:
  427. return RuntimeError("All referenced class doesn't have the\
  428. same datasource. Query not possible")
  429. if ref_field in ref_class.fieldnames(True):
  430. ref_dict[ref_class] = ref_field
  431. else:
  432. msg = "Warning the class %s is not considered in \
  433. the relational filter %s"
  434. msg %= (ref_class.__name__, ref_field)
  435. logger.debug(msg)
  436. if len(ref_dict) == 0:
  437. return NameError( "No field named '%s' in referenced objects %s"
  438. % (ref_field, ref_class.__name__))
  439. return (fieldname, ref_dict)
  440. ##@brief A query to insert a new object
  441. class LeInsertQuery(LeQuery):
  442. _hook_prefix = 'leapi_insert_'
  443. _data_check_args = { 'complete': True, 'allow_internal': False }
  444. def __init__(self, target_class):
  445. super().__init__(target_class)
  446. ## @brief Implements an insert query operation, with only one insertion
  447. # @param new_datas : datas to be inserted
  448. def __query(self, new_datas):
  449. nb_inserted = self._datasource.insert(self._target_class,new_datas)
  450. if nb_inserted < 0:
  451. raise LeQueryError("Insertion error")
  452. return nb_inserted
  453. """
  454. ## @brief Implements an insert query operation, with multiple insertions
  455. # @param datas : list of **datas to be inserted
  456. def __query(self, datas):
  457. nb_inserted = self._datasource.insert_multi(
  458. self._target_class,datas_list)
  459. if nb_inserted < 0:
  460. raise LeQueryError("Multiple insertions error")
  461. return nb_inserted
  462. """
  463. ## @brief Execute the insert query
  464. def execute(self, new_datas):
  465. return super().execute(new_datas)
  466. ##@brief A query to update datas for a given object
  467. class LeUpdateQuery(LeFilteredQuery):
  468. _hook_prefix = 'leapi_update_'
  469. _data_check_args = { 'complete': True, 'allow_internal': False }
  470. def __init__(self, target_class, query_filter):
  471. super().__init__(target_class, query_filter)
  472. ##@brief Implements an update query
  473. #@param filters list : see @ref LeFilteredQuery
  474. #@param rel_filters list : see @ref LeFilteredQuery
  475. #@param updated_datas dict : datas to update
  476. #@returns the number of updated items
  477. def __query(self, filters, rel_filters, updated_datas):
  478. nb_updated = self._datasource.update(
  479. self._target_class, filters, rel_filters, update_datas)
  480. return nb_updated
  481. ## @brief Execute the update query
  482. def execute(self, updated_datas):
  483. return super().execute(updated_datas)
  484. ##@brief A query to delete an object
  485. class LeDeleteQuery(LeFilteredQuery):
  486. _hook_prefix = 'leapi_delete_'
  487. def __init__(self, target_class, query_filter):
  488. super().__init__(target_class, query_filter)
  489. ## @brief Execute the delete query
  490. def execute(self):
  491. return super().execute()
  492. ##@brief Implements delete query operations
  493. #@param filters list : see @ref LeFilteredQuery
  494. #@param rel_filters list : see @ref LeFilteredQuery
  495. #@returns the number of deleted items
  496. def __query(self, filters, rel_filters):
  497. nb_deleted = datasource.delete(
  498. self._target_class, filters, rel_filters)
  499. return nb_deleted
  500. class LeGetQuery(LeFilteredQuery):
  501. _hook_prefix = 'leapi_get_'
  502. ##@brief Instanciate a new get query
  503. #@param target_class LeObject : class of object the query is about
  504. #@param query_filters dict : {OP, list of query filters }
  505. # or tuple (FIELD, OPERATOR, VALUE) )
  506. #@param field_list list|None : list of string representing fields see
  507. # @ref leobject_filters
  508. #@param order list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  509. #@param group list : A list of field names or tuple (FIELDNAME,[ASC | DESC])
  510. #@param limit int : The maximum number of returned results
  511. #@param offset int : offset
  512. def __init__(self, target_class, query_filter, **kwargs):
  513. super().__init__(target_class, query_filter)
  514. ##@brief The fields to get
  515. self.__field_list = None
  516. ##@brief An equivalent to the SQL ORDER BY
  517. self.__order = None
  518. ##@brief An equivalent to the SQL GROUP BY
  519. self.__group = None
  520. ##@brief An equivalent to the SQL LIMIT x
  521. self.__limit = None
  522. ##@brief An equivalent to the SQL LIMIT x, OFFSET
  523. self.__offset = 0
  524. # Checking kwargs and assigning default values if there is some
  525. for argname in kwargs:
  526. if argname not in (
  527. 'field_list', 'order', 'group', 'limit', 'offset'):
  528. raise TypeError("Unexpected argument '%s'" % argname)
  529. if 'field_list' not in kwargs:
  530. self.set_field_list(target_class.fieldnames(include_ro = True))
  531. else:
  532. self.set_field_list(kwargs['field_list'])
  533. if 'order' in kwargs:
  534. #check kwargs['order']
  535. self.__order = kwargs['order']
  536. if 'group' in kwargs:
  537. #check kwargs['group']
  538. self.__group = kwargs['group']
  539. if 'limit' in kwargs:
  540. try:
  541. self.__limit = int(kwargs[limit])
  542. if self.__limit <= 0:
  543. raise ValueError()
  544. except ValueError:
  545. msg = "limit argument expected to be an interger > 0"
  546. raise ValueError(msg)
  547. if 'offset' in kwargs:
  548. try:
  549. self.__offset = int(kwargs['offset'])
  550. if self.__offset < 0:
  551. raise ValueError()
  552. except ValueError:
  553. msg = "offset argument expected to be an integer >= 0"
  554. raise ValueError(msg)
  555. ##@brief Set the field list
  556. # @param field_list list | None : If None use all fields
  557. # @return None
  558. # @throw LeQueryError if unknown field given
  559. def set_field_list(self, field_list):
  560. err_l = dict()
  561. for fieldname in field_list:
  562. ret = self._check_field(self._target_class, fieldname)
  563. if isinstance(ret, Exception):
  564. msg = "No field named '%s' in %s"
  565. msg %= (fieldname, self._target_class.__name__)
  566. expt = NameError(msg)
  567. err_l[fieldname] = expt
  568. if len(err_l) > 0:
  569. msg = "Error while setting field_list in a get query"
  570. raise LeQueryError(msg = msg, exceptions = err_l)
  571. self.__field_list = list(set(field_list))
  572. ##@brief Execute the get query
  573. def execute(self):
  574. return super().execute()
  575. ##@brief Implements select query operations
  576. # @returns a list containing the item(s)
  577. def __query(self, datasource):
  578. # select datas corresponding to query_filter
  579. l_datas=datasource.select( self._target_class,
  580. list(self.field_list),
  581. self.query_filter,
  582. None,
  583. self.__order,
  584. self.__group,
  585. self.__limit,
  586. self.offset,
  587. False)
  588. return l_datas
  589. ##@return a dict with query infos
  590. def dump_infos(self):
  591. ret = super().dump_infos()
  592. ret.update( { 'field_list' : self.__field_list,
  593. 'order' : self.__order,
  594. 'group' : self.__group,
  595. 'limit' : self.__limit,
  596. 'offset': self.__offset,
  597. })
  598. return ret
  599. def __repr__(self):
  600. res = "<LeGetQuery target={target_class} filter={query_filter} \
  601. field_list={field_list} order={order} group={group} limit={limit} \
  602. offset={offset}"
  603. res = res.format(**self.dump_infos())
  604. if len(self.subqueries) > 0:
  605. for n,subq in enumerate(self.subqueries):
  606. res += "\n\tSubquerie %d : %s"
  607. res %= (n, subq)
  608. res += ">"
  609. return res