|
1 | | -#$Id: back_mysql.py,v 1.68 2006-04-27 03:40:42 richard Exp $ |
| 1 | +#$Id: back_mysql.py,v 1.69 2006-07-07 15:04:28 schlatterbeck Exp $ |
2 | 2 | # |
3 | 3 | # Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev <[email protected]> |
4 | 4 | # |
@@ -550,275 +550,13 @@ def sql_close(self): |
550 | 550 | raise |
551 | 551 |
|
552 | 552 | class MysqlClass: |
553 | | - # we're overriding this method for ONE missing bit of functionality. |
554 | | - # look for "I can't believe it's not a toy RDBMS" below |
555 | | - def filter(self, search_matches, filterspec, sort=(None,None), |
556 | | - group=(None,None)): |
557 | | - '''Return a list of the ids of the active nodes in this class that |
558 | | - match the 'filter' spec, sorted by the group spec and then the |
559 | | - sort spec |
560 | | -
|
561 | | - "filterspec" is {propname: value(s)} |
562 | | -
|
563 | | - "sort" and "group" are (dir, prop) where dir is '+', '-' or None |
564 | | - and prop is a prop name or None |
565 | | -
|
566 | | - "search_matches" is {nodeid: marker} or None |
567 | | -
|
568 | | - The filter must match all properties specificed. If the property |
569 | | - value to match is a list: |
570 | | -
|
571 | | - 1. String properties must match all elements in the list, and |
572 | | - 2. Other properties must match any of the elements in the list. |
| 553 | + def _subselect (self, cn, tn) : |
| 554 | + ''' "I can't believe it's not a toy RDBMS" |
| 555 | + see, even toy RDBMSes like gadfly and sqlite can do sub-selects... |
573 | 556 | ''' |
574 | | - # we can't match anything if search_matches is empty |
575 | | - if search_matches == {}: |
576 | | - return [] |
577 | | - |
578 | | - if __debug__: |
579 | | - start_t = time.time() |
580 | | - |
581 | | - cn = self.classname |
582 | | - |
583 | | - # vars to hold the components of the SQL statement |
584 | | - frum = ['_'+cn] # FROM clauses |
585 | | - loj = [] # LEFT OUTER JOIN clauses |
586 | | - where = [] # WHERE clauses |
587 | | - args = [] # *any* positional arguments |
588 | | - a = self.db.arg |
589 | | - |
590 | | - # figure the WHERE clause from the filterspec |
591 | | - props = self.getprops() |
592 | | - mlfilt = 0 # are we joining with Multilink tables? |
593 | | - for k, v in filterspec.items(): |
594 | | - propclass = props[k] |
595 | | - # now do other where clause stuff |
596 | | - if isinstance(propclass, Multilink): |
597 | | - mlfilt = 1 |
598 | | - tn = '%s_%s'%(cn, k) |
599 | | - if v in ('-1', ['-1']): |
600 | | - # only match rows that have count(linkid)=0 in the |
601 | | - # corresponding multilink table) |
602 | | - |
603 | | - # "I can't believe it's not a toy RDBMS" |
604 | | - # see, even toy RDBMSes like gadfly and sqlite can do |
605 | | - # sub-selects... |
606 | | - self.db.sql('select nodeid from %s'%tn) |
607 | | - s = ','.join([x[0] for x in self.db.sql_fetchall()]) |
608 | | - |
609 | | - where.append('_%s.id not in (%s)'%(cn, s)) |
610 | | - elif isinstance(v, type([])): |
611 | | - frum.append(tn) |
612 | | - s = ','.join([a for x in v]) |
613 | | - where.append('_%s.id=%s.nodeid and %s.linkid in (%s)'%(cn, |
614 | | - tn, tn, s)) |
615 | | - args = args + v |
616 | | - else: |
617 | | - frum.append(tn) |
618 | | - where.append('_%s.id=%s.nodeid and %s.linkid=%s'%(cn, tn, |
619 | | - tn, a)) |
620 | | - args.append(v) |
621 | | - elif k == 'id': |
622 | | - if isinstance(v, type([])): |
623 | | - s = ','.join([a for x in v]) |
624 | | - where.append('_%s.%s in (%s)'%(cn, k, s)) |
625 | | - args = args + v |
626 | | - else: |
627 | | - where.append('_%s.%s=%s'%(cn, k, a)) |
628 | | - args.append(v) |
629 | | - elif isinstance(propclass, String): |
630 | | - if not isinstance(v, type([])): |
631 | | - v = [v] |
632 | | - |
633 | | - # Quote the bits in the string that need it and then embed |
634 | | - # in a "substring" search. Note - need to quote the '%' so |
635 | | - # they make it through the python layer happily |
636 | | - v = ['%%'+self.db.sql_stringquote(s)+'%%' for s in v] |
637 | | - |
638 | | - # now add to the where clause |
639 | | - where.append('(' |
640 | | - +' and '.join(["_%s._%s LIKE '%s'"%(cn, k, s) for s in v]) |
641 | | - +')') |
642 | | - # note: args are embedded in the query string now |
643 | | - elif isinstance(propclass, Link): |
644 | | - if isinstance(v, type([])): |
645 | | - d = {} |
646 | | - for entry in v: |
647 | | - if entry == '-1': |
648 | | - entry = None |
649 | | - d[entry] = entry |
650 | | - l = [] |
651 | | - if d.has_key(None) or not d: |
652 | | - del d[None] |
653 | | - l.append('_%s._%s is NULL'%(cn, k)) |
654 | | - if d: |
655 | | - v = d.keys() |
656 | | - s = ','.join([a for x in v]) |
657 | | - l.append('(_%s._%s in (%s))'%(cn, k, s)) |
658 | | - args = args + v |
659 | | - if l: |
660 | | - where.append('(' + ' or '.join(l) +')') |
661 | | - else: |
662 | | - if v in ('-1', None): |
663 | | - v = None |
664 | | - where.append('_%s._%s is NULL'%(cn, k)) |
665 | | - else: |
666 | | - where.append('_%s._%s=%s'%(cn, k, a)) |
667 | | - args.append(v) |
668 | | - elif isinstance(propclass, Date): |
669 | | - dc = self.db.hyperdb_to_sql_value[hyperdb.Date] |
670 | | - if isinstance(v, type([])): |
671 | | - s = ','.join([a for x in v]) |
672 | | - where.append('_%s._%s in (%s)'%(cn, k, s)) |
673 | | - args = args + [dc(date.Date(x)) for x in v] |
674 | | - else: |
675 | | - try: |
676 | | - # Try to filter on range of dates |
677 | | - date_rng = propclass.range_from_raw (v, self.db) |
678 | | - if date_rng.from_value: |
679 | | - where.append('_%s._%s >= %s'%(cn, k, a)) |
680 | | - args.append(dc(date_rng.from_value)) |
681 | | - if date_rng.to_value: |
682 | | - where.append('_%s._%s <= %s'%(cn, k, a)) |
683 | | - args.append(dc(date_rng.to_value)) |
684 | | - except ValueError: |
685 | | - # If range creation fails - ignore that search parameter |
686 | | - pass |
687 | | - elif isinstance(propclass, Interval): |
688 | | - # filter using the __<prop>_int__ column |
689 | | - if isinstance(v, type([])): |
690 | | - s = ','.join([a for x in v]) |
691 | | - where.append('_%s.__%s_int__ in (%s)'%(cn, k, s)) |
692 | | - args = args + [date.Interval(x).as_seconds() for x in v] |
693 | | - else: |
694 | | - try: |
695 | | - # Try to filter on range of intervals |
696 | | - date_rng = Range(v, date.Interval) |
697 | | - if date_rng.from_value: |
698 | | - where.append('_%s.__%s_int__ >= %s'%(cn, k, a)) |
699 | | - args.append(date_rng.from_value.as_seconds()) |
700 | | - if date_rng.to_value: |
701 | | - where.append('_%s.__%s_int__ <= %s'%(cn, k, a)) |
702 | | - args.append(date_rng.to_value.as_seconds()) |
703 | | - except ValueError: |
704 | | - # If range creation fails - ignore that search parameter |
705 | | - pass |
706 | | - else: |
707 | | - if isinstance(v, type([])): |
708 | | - s = ','.join([a for x in v]) |
709 | | - where.append('_%s._%s in (%s)'%(cn, k, s)) |
710 | | - args = args + v |
711 | | - else: |
712 | | - where.append('_%s._%s=%s'%(cn, k, a)) |
713 | | - args.append(v) |
714 | | - |
715 | | - # don't match retired nodes |
716 | | - where.append('_%s.__retired__ <> 1'%cn) |
717 | | - |
718 | | - # add results of full text search |
719 | | - if search_matches is not None: |
720 | | - v = search_matches.keys() |
721 | | - s = ','.join([a for x in v]) |
722 | | - where.append('_%s.id in (%s)'%(cn, s)) |
723 | | - args = args + v |
724 | | - |
725 | | - # sanity check: sorting *and* grouping on the same property? |
726 | | - if group[1] == sort[1]: |
727 | | - sort = (None, None) |
728 | | - |
729 | | - # "grouping" is just the first-order sorting in the SQL fetch |
730 | | - orderby = [] |
731 | | - ordercols = [] |
732 | | - mlsort = [] |
733 | | - rhsnum = 0 |
734 | | - for sortby in group, sort: |
735 | | - sdir, prop = sortby |
736 | | - if sdir and prop: |
737 | | - if isinstance(props[prop], Multilink): |
738 | | - mlsort.append(sortby) |
739 | | - continue |
740 | | - elif isinstance(props[prop], Interval): |
741 | | - # use the int column for sorting |
742 | | - o = '__'+prop+'_int__' |
743 | | - ordercols.append(o) |
744 | | - elif isinstance(props[prop], Link): |
745 | | - # determine whether the linked Class has an order property |
746 | | - lcn = props[prop].classname |
747 | | - link = self.db.classes[lcn] |
748 | | - o = '_%s._%s'%(cn, prop) |
749 | | - op = link.orderprop () |
750 | | - if op != 'id': |
751 | | - tn = '_' + lcn |
752 | | - rhs = 'rhs%s_'%rhsnum |
753 | | - rhsnum += 1 |
754 | | - loj.append('LEFT OUTER JOIN %s as %s on %s=%s.id'%( |
755 | | - tn, rhs, o, rhs)) |
756 | | - o = '%s._%s'%(rhs, op) |
757 | | - ordercols.append(o) |
758 | | - elif prop == 'id': |
759 | | - o = '_%s.id'%cn |
760 | | - else: |
761 | | - o = '_%s._%s'%(cn, prop) |
762 | | - ordercols.append(o) |
763 | | - if sdir == '-': |
764 | | - o += ' desc' |
765 | | - orderby.append(o) |
766 | | - |
767 | | - # construct the SQL |
768 | | - frum = ','.join(frum) |
769 | | - if where: |
770 | | - where = ' where ' + (' and '.join(where)) |
771 | | - else: |
772 | | - where = '' |
773 | | - if mlfilt: |
774 | | - # we're joining tables on the id, so we will get dupes if we |
775 | | - # don't distinct() |
776 | | - cols = ['distinct(_%s.id)'%cn] |
777 | | - else: |
778 | | - cols = ['_%s.id'%cn] |
779 | | - if orderby: |
780 | | - cols = cols + ordercols |
781 | | - order = ' order by %s'%(','.join(orderby)) |
782 | | - else: |
783 | | - order = '' |
784 | | - cols = ','.join(cols) |
785 | | - loj = ' '.join(loj) |
786 | | - sql = 'select %s from %s %s %s%s'%(cols, frum, loj, where, order) |
787 | | - args = tuple(args) |
788 | | - self.db.sql(sql, args) |
789 | | - l = self.db.cursor.fetchall() |
790 | | - |
791 | | - # return the IDs (the first column) |
792 | | - # XXX numeric ids |
793 | | - l = [str(row[0]) for row in l] |
794 | | - |
795 | | - if not mlsort: |
796 | | - if __debug__: |
797 | | - self.db.stats['filtering'] += (time.time() - start_t) |
798 | | - return l |
799 | | - |
800 | | - # ergh. someone wants to sort by a multilink. |
801 | | - r = [] |
802 | | - for id in l: |
803 | | - m = [] |
804 | | - for ml in mlsort: |
805 | | - m.append(self.get(id, ml[1])) |
806 | | - r.append((id, m)) |
807 | | - i = 0 |
808 | | - for sortby in mlsort: |
809 | | - def sortfun(a, b, dir=sortby[i], i=i): |
810 | | - if dir == '-': |
811 | | - return cmp(b[1][i], a[1][i]) |
812 | | - else: |
813 | | - return cmp(a[1][i], b[1][i]) |
814 | | - r.sort(sortfun) |
815 | | - i += 1 |
816 | | - r = [i[0] for i in r] |
817 | | - |
818 | | - if __debug__: |
819 | | - self.db.stats['filtering'] += (time.time() - start_t) |
820 | | - |
821 | | - return r |
| 557 | + self.db.sql('select nodeid from %s'%tn) |
| 558 | + s = ','.join([x[0] for x in self.db.sql_fetchall()]) |
| 559 | + return '_%s.id not in (%s)'%(cn, s) |
822 | 560 |
|
823 | 561 | class Class(MysqlClass, rdbms_common.Class): |
824 | 562 | pass |
|
0 commit comments