Mercurial > p > roundup > code
comparison roundup/hyperdb.py @ 6403:9957d8d10783
Tests and bug-fix for issue2551119
.. and some other failing tests I came up with when trying to reproduce
the problem of the issue.
| author | Ralf Schlatterbeck <rsc@runtux.com> |
|---|---|
| date | Mon, 10 May 2021 16:19:37 +0200 |
| parents | 619807d9a2df |
| children | ce99e0d39262 |
comparison
equal
deleted
inserted
replaced
| 6402:619807d9a2df | 6403:9957d8d10783 |
|---|---|
| 463 is a dictionary containing one or several of the values 'sort', | 463 is a dictionary containing one or several of the values 'sort', |
| 464 'search', 'retrieve'. | 464 'search', 'retrieve'. |
| 465 | 465 |
| 466 The Proptree is also used for transitively searching attributes for | 466 The Proptree is also used for transitively searching attributes for |
| 467 backends that do not support transitive search (e.g. anydbm). The | 467 backends that do not support transitive search (e.g. anydbm). The |
| 468 _val attribute with set_val is used for this. | 468 val attribute with set_val is used for this. |
| 469 """ | 469 """ |
| 470 | 470 |
| 471 def __init__(self, db, cls, name, props, parent=None, retr=False): | 471 def __init__(self, db, cls, name, props, parent=None, retr=False): |
| 472 self.db = db | 472 self.db = db |
| 473 self.name = name | 473 self.name = name |
| 474 self.props = props | 474 self.props = props |
| 475 self.parent = parent | 475 self.parent = parent |
| 476 self._val = None | 476 self.val = None |
| 477 self.has_values = False | 477 self.has_values = False |
| 478 self.has_result = False | |
| 478 self.cls = cls | 479 self.cls = cls |
| 479 self.classname = None | 480 self.classname = None |
| 480 self.uniqname = None | 481 self.uniqname = None |
| 481 self.children = [] | 482 self.children = [] |
| 482 self.sortattr = [] | 483 self.sortattr = [] |
| 632 by_id[x].add(id) | 633 by_id[x].add(id) |
| 633 for k in by_id: | 634 for k in by_id: |
| 634 if expr.evaluate(by_id[k]): | 635 if expr.evaluate(by_id[k]): |
| 635 items.add(k) | 636 items.add(k) |
| 636 | 637 |
| 638 # The subquery has found nothing. So it doesn't make | |
| 639 # sense to search further. | |
| 640 if not items: | |
| 641 self.set_val([], force=True) | |
| 642 return self.val | |
| 637 filterspec[p.name] = list(sorted(items, key=int)) | 643 filterspec[p.name] = list(sorted(items, key=int)) |
| 638 elif isinstance(p.val, type([])): | 644 elif isinstance(p.val, type([])): |
| 639 exact = [] | 645 exact = [] |
| 640 subst = [] | 646 subst = [] |
| 641 for v in p.val: | 647 for v in p.val: |
| 646 if exact: | 652 if exact: |
| 647 exact_match_spec[p.name] = exact | 653 exact_match_spec[p.name] = exact |
| 648 if subst: | 654 if subst: |
| 649 filterspec[p.name] = subst | 655 filterspec[p.name] = subst |
| 650 elif not exact: # don't set if we have exact criteria | 656 elif not exact: # don't set if we have exact criteria |
| 651 filterspec[p.name] =[ '-1' ] # no match was found | 657 if p.has_result: |
| 658 # A subquery already has found nothing. So | |
| 659 # it doesn't make sense to search further. | |
| 660 self.set_val([], force=True) | |
| 661 return self.val | |
| 662 else: | |
| 663 filterspec[p.name] = ['-1'] # no match was found | |
| 652 else: | 664 else: |
| 653 assert not isinstance(p.val, Exact_Match) | 665 assert not isinstance(p.val, Exact_Match) |
| 654 filterspec[p.name] = p.val | 666 filterspec[p.name] = p.val |
| 655 self.val = self.cls._filter(search_matches, filterspec, sort and self, | 667 self.set_val(self.cls._filter(search_matches, filterspec, sort and self, |
| 656 retired=retired, | 668 retired=retired, |
| 657 exact_match_spec=exact_match_spec) | 669 exact_match_spec=exact_match_spec)) |
| 658 return self.val | 670 return self.val |
| 659 | 671 |
| 660 def sort(self, ids=None): | 672 def sort(self, ids=None): |
| 661 """ Sort ids by the order information stored in self. With | 673 """ Sort ids by the order information stored in self. With |
| 662 optimisations: Some order attributes may be precomputed (by the | 674 optimisations: Some order attributes may be precomputed (by the |
| 740 pt.sort_ids = None | 752 pt.sort_ids = None |
| 741 for pt in self.sortattr: | 753 for pt in self.sortattr: |
| 742 pt.sort_result = None | 754 pt.sort_result = None |
| 743 return ids | 755 return ids |
| 744 | 756 |
| 745 def _set_val(self, val): | 757 def set_val(self, val, force=False, result=True): |
| 746 """ Check if self._val is already defined. If yes, we compute the | 758 """ Check if self.val is already defined (it is not None and |
| 759 has_values is True). If yes, we compute the | |
| 747 intersection of the old and the new value(s) | 760 intersection of the old and the new value(s) |
| 748 Note: If self is a Leaf node we need to compute a | 761 Note: If self is a Leaf node we need to compute a |
| 749 union: Normally we intersect (logical and) different | 762 union: Normally we intersect (logical and) different |
| 750 subqueries into a Link or Multilink property. But for | 763 subqueries into a Link or Multilink property. But for |
| 751 leaves we might have a part of a query in a filterspec and | 764 leaves we might have a part of a query in a filterspec and |
| 752 in an exact_match_spec. These have to be all there, the | 765 in an exact_match_spec. These have to be all there, the |
| 753 generated search will ensure a logical and of all tests for | 766 generated search will ensure a logical and of all tests for |
| 754 equality/substring search. | 767 equality/substring search. |
| 755 """ | 768 """ |
| 769 if force: | |
| 770 assert val == [] | |
| 771 assert result | |
| 772 self.val = val | |
| 773 self.has_values = True | |
| 774 self.has_result = True | |
| 775 return | |
| 756 if self.has_values: | 776 if self.has_values: |
| 757 v = self._val | 777 v = self.val |
| 758 if not isinstance(self._val, type([])): | 778 if not isinstance(self.val, type([])): |
| 759 v = [self._val] | 779 v = [self.val] |
| 760 vals = set(v) | 780 vals = set(v) |
| 761 if not isinstance(val, type([])): | 781 if not isinstance(val, type([])): |
| 762 val = [val] | 782 val = [val] |
| 783 if self.has_result: | |
| 784 assert result | |
| 763 # if cls is None we're a leaf | 785 # if cls is None we're a leaf |
| 764 if self.cls: | 786 if self.cls: |
| 765 vals.intersection_update(val) | 787 vals.intersection_update(val) |
| 766 else: | 788 else: |
| 767 vals.update(val) | 789 vals.update(val) |
| 768 self._val = [v for v in vals] | 790 self.val = list(vals) |
| 769 else: | 791 else: |
| 770 self._val = val | 792 # If a subquery found nothing we don't care if there is an |
| 793 # expression | |
| 794 if not self.has_values or not val: | |
| 795 self.val = val | |
| 796 if result: | |
| 797 self.has_result = True | |
| 798 else: | |
| 799 if not result: | |
| 800 assert not self.cls | |
| 801 vals.update(val) | |
| 802 self.val = list(vals) | |
| 803 else: | |
| 804 assert self.cls | |
| 805 is_expression = min(int(i) for i in self.val) < -1 | |
| 806 if is_expression: | |
| 807 # Tag on the ORed values with an AND | |
| 808 l = val | |
| 809 for i in range(len(val)-1): | |
| 810 l.append('-4') | |
| 811 l.append('-3') | |
| 812 self.val = self.val + l | |
| 813 else: | |
| 814 vals.intersection_update(val) | |
| 815 self.val = list(vals) | |
| 816 self.has_result = True | |
| 771 self.has_values = True | 817 self.has_values = True |
| 772 | |
| 773 val = property(lambda self: self._val, _set_val) | |
| 774 | 818 |
| 775 def _sort(self, val): | 819 def _sort(self, val): |
| 776 """Finally sort by the given sortattr.sort_result. Note that we | 820 """Finally sort by the given sortattr.sort_result. Note that we |
| 777 do not sort by attrs having attr_sort_done set. The caller is | 821 do not sort by attrs having attr_sort_done set. The caller is |
| 778 responsible for setting attr_sort_done only for trailing | 822 responsible for setting attr_sort_done only for trailing |
| 1540 if exact: | 1584 if exact: |
| 1541 if isinstance(v, type([])): | 1585 if isinstance(v, type([])): |
| 1542 vv = [] | 1586 vv = [] |
| 1543 for x in v: | 1587 for x in v: |
| 1544 vv.append(Exact_Match(x)) | 1588 vv.append(Exact_Match(x)) |
| 1545 p.val = vv | 1589 p.set_val(vv, result=False) |
| 1546 else: | 1590 else: |
| 1547 p.val = [Exact_Match(v)] | 1591 p.set_val([Exact_Match(v)], result=False) |
| 1548 else: | 1592 else: |
| 1549 p.val = v | 1593 p.set_val(v, result=False) |
| 1550 multilinks = {} | 1594 multilinks = {} |
| 1551 for s in sortattr: | 1595 for s in sortattr: |
| 1552 keys = s[1].split('.') | 1596 keys = s[1].split('.') |
| 1553 p = proptree | 1597 p = proptree |
| 1554 mlseen = False | 1598 mlseen = False |
