Ticket #3691: ticket_3691_axiak.diff
File ticket_3691_axiak.diff, 7.2 KB (added by , 18 years ago) |
---|
-
db/models/query.py
636 636 637 637 class QOperator(object): 638 638 "Base class for QAnd and QOr" 639 underneath_an_or = False # used to track ORs 640 checked_or = False 641 642 def search_for_or(self): 643 " Returns true if an or exists beneath this Q operator. " 644 if type(self) == QOr: 645 return True 646 647 for val in self.args: 648 if val.search_for_or(): 649 return True 650 651 return False # there was no OR below this. 652 653 def set_or_found(self, or_value): 654 if self.checked_or: 655 return 656 657 self.underneath_an_or = or_value 658 self.checked_or = True 659 660 for val in self.args: 661 val.set_or_found(or_value) # set for all the children 662 639 663 def __init__(self, *args): 640 664 self.args = args 641 665 642 666 def get_sql(self, opts): 667 668 if not self.checked_or: 669 found_an_or = self.search_for_or() 670 self.set_or_found(found_an_or) 671 672 # now that we've finished building the query 673 self.checked_or = False 674 643 675 joins, where, params = SortedDict(), [], [] 644 676 for val in self.args: 645 677 try: … … 684 716 685 717 class Q(object): 686 718 "Encapsulates queries as objects that can be combined logically." 719 underneath_an_or = False 720 checked_or = False 721 outer_join = 'LEFT OUTER JOIN' 722 inner_join = 'INNER JOIN' 723 687 724 def __init__(self, **kwargs): 688 725 self.kwargs = kwargs 689 726 … … 693 730 def __or__(self, other): 694 731 return QOr(self, other) 695 732 733 def search_for_or(self): 734 ' Returns false, since there are no OR Qs below. ' 735 return False 736 737 def set_or_found(self, or_found): 738 """ Set whether or not we have an OR above us. """ 739 if not self.checked_or: 740 self.underneath_an_or = or_found 741 self.checked_or = True # we checked for an OR 742 696 743 def get_sql(self, opts): 697 return parse_lookup(self.kwargs.items(), opts) 744 # we will check to see if it's an OR, and change the join 745 # based upon that 698 746 747 if self.underneath_an_or: 748 join_text = self.outer_join 749 else: 750 join_text = self.inner_join 751 752 joins, where, params = parse_lookup(self.kwargs.items(), opts) 753 754 joins2 = {} 755 for item, key in joins.items(): 756 # now we will fix the joins dictionary with 757 # the new type of join 758 joins2[item] = (key[0], join_text, key[2]) 759 760 # now that we've done creating the query, 761 # it's best that we let ourselves search again 762 self.checked_or = False 763 764 return joins2, where, params 765 766 class QSplit(Q): 767 """ 768 Encapsulates a single JOIN-type query into one object. 769 This allows you to run an AND on a m2m query: 770 If Article has a m2m relation with Tag and you run: 771 QSplit(Q(tag__value = 'A')) & QSplit(Q(tag__value = 'B')) 772 The above will filter for all articles with a tag with value 773 'A' *and* a tag with a value 'B'. 774 """ 775 776 def __init__(self, q): 777 " Creates a separate un-joinable Q from the Q passed in. " 778 self.q = q 779 780 def search_for_or(self): 781 ' Search for an OR beneath this QNot. ' 782 return self.q.search_for_or() 783 784 def set_or_found(self, or_found): 785 """ Set whether or not we have an OR above us. """ 786 if not self.checked_or: 787 self.underneath_an_or = or_found 788 self.checked_or = True # we checked for an OR 789 790 self.q.set_or_found(or_found) # now we have to make the change to our child 791 792 def get_sql(self, opts): 793 from django.conf import settings 794 # the quote below depends on the database engine 795 quote = settings.DATABASE_ENGINE == 'mysql' and "`" or '"' 796 797 joins, where, params = self.q.get_sql(opts) 798 799 key_replace = {} 800 joins2 = SortedDict() 801 where2 = [] 802 803 UNIQUE_HASH = hash(self) # this is the unique key 804 805 for key, val in joins.items(): 806 cur_key = key.strip(quote) # set the current key 807 cur_val = '%s__%s' % (cur_key, UNIQUE_HASH) 808 809 key_replace[cur_key] = cur_val # add to the list of thigs to replace 810 811 joins2['%s%s%s' % (quote, cur_val, quote)] = val 812 813 # we need 2 passes since now we need to go back and replace any 814 # leftover table names 815 for key, val in joins2.items(): 816 joins2[key] = (val[0],val[1],replace_by_dict(val[2], key_replace)) 817 818 where2 = [replace_by_dict(clause, key_replace) for clause in where] 819 820 return joins2, where2, params 821 822 699 823 class QNot(Q): 700 824 "Encapsulates NOT (...) queries as objects" 701 825 def __init__(self, q): 702 826 "Creates a negation of the q object passed in." 703 827 self.q = q 704 828 829 def search_for_or(self): 830 ' Search for an OR beneath this QNot. ' 831 return self.q.search_for_or() 832 833 def set_or_found(self, or_found): 834 """ Set whether or not we have an OR above us. """ 835 if not self.checked_or: 836 self.underneath_an_or = or_found 837 self.checked_or = True # we checked for an OR 838 839 self.q.set_or_found(or_found) # now we have to make the change to our child 840 705 841 def get_sql(self, opts): 706 842 try: 707 843 joins, where, params = self.q.get_sql(opts) … … 776 912 (qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column))) 777 913 select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields]) 778 914 fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen, max_depth, cur_depth+1) 915 916 def replace_by_dict(string, dict): 917 """ 918 This will take a dictionary of strings to replace and a string and perform the replacement. 919 This is a helper function for QSplit. 920 It takes a dictionary of original->replacements and performs the replacements 921 in a safe, sane mannor. That is, it won't replace a string twice and it will 922 start with the largest original strings and work its way downward. 923 """ 924 if len(dict) == 0: # we have no more strings to replace 925 return string 779 926 927 curDict = dict.copy() 928 keys = curDict.keys() 929 keys.sort(lambda x,y: cmp(len(y), len(x))) # sort the keys by length 930 key = keys[0] 931 val = curDict[key] 932 933 del curDict[key] 934 935 offset = 0 936 strings = [string] 937 938 939 while strings[-1].find(key) != -1: 940 loc = strings[-1].find(key) 941 strings.append(strings[-1][len(key) + loc:]) 942 strings[-2] = strings[-2][:loc] 943 944 return val.join([replaceDict(s,curDict) for s in strings]) 945 946 947 948 780 949 def parse_lookup(kwarg_items, opts): 781 950 # Helper function that handles converting API kwargs 782 951 # (e.g. "name__exact": "tom") to SQL.