
""" sql semantics
"""

### trim unused methods.
### make assns use equivalence classes.

### maybe eventually implement disj-conj-eq optimizations

### note: for multithreading x.relbind(...) should ALWAYs return
###   a fresh copy of structure (sometimes in-place now).

### note: binding of order by is dubious with archiving,
###    should not bind IN PLACE, leave unbound elts alone!

### need to fix serialization/deserialization of btand and btor
###

# use kjbuckets builtin if available
try:
    import kjbuckets
except ImportError:
    import kjbuckets0
    kjbuckets = kjbuckets0

Tuple = kjbuckets.kjDict
Graph = kjbuckets.kjGraph
Set = kjbuckets.kjSet

import sys, traceback
### debug
#sys.stderr = sys.stdin

# operations on simple tuples, mostly from kjbuckets
#def maketuple(thing):
#    """try to make a tuple from thing.
#       thing should be a dictionary or sequence of (name, value)
#       or other tuple."""
#    from types import DictType
#    if type(thing)==DictType:
#       return Tuple(thing.items() )
#    else: return Tuple(thing)

def no_ints_nulls(list):
    """in place remove all ints, Nones from a list (for null handling)"""
    tt = type
    nn = None
    from types import IntType
    count = 0
    for x in list:
        if tt(x) is not IntType and x is not nn:
            list[count] = x
            count = count+1
    del list[count:]
    return list

# stuff for bound tuples.

class HashJoiner:

    def __init__(self, bt, relname, attributes, relation, witness):
        self.relname = relname
        self.attributes = attributes
        self.relation = relation
        self.witness = witness
        self.bt = bt
        eqs = bt.eqs
        #print "relname", relname
        #print "attributes", attributes
        #print "relation", relation
        #print "witness", witness
        #print "bt", bt
        transform = self.transform = kjbuckets.kjDict()
        rbindings = self.rbindings = kjbuckets.kjSet()
        for a in attributes:
            b = (relname, a)
            transform[b] = a
            rbindings[b] = b
        self.eqs = eqs = eqs + kjbuckets.kjGraph(rbindings)
        witness = witness.remap(eqs)
        known = kjbuckets.kjSet(witness.keys()) & rbindings
        batts = tuple(known.items())
        if not batts:
            atts = ()
        elif len(batts)==1:
            atts = ( transform[batts[0]], )
        else:
            atts = transform.dump(batts)
        self.atts = atts
        self.batts = batts
        self.transform = transform
        eqs = bt.eqs
        #eqs = (rbindings * eqs)
        self.eqs = eqs = eqs + kjbuckets.kjGraph(rbindings)
        self.transformG = transformG = eqs * transform
        assns = self.assns = bt.assns
        self.rassns = assns.remap( ~transformG )

    def relbind(self, db, atts):
        rel = self.relation
        #print "rel is ", rel, type(rel)
        #print dir(rel)
        if rel.is_view:
            self.relation = rel.relbind(db, atts)
        return self

    def uncache(self):
        rel = self.relation
        if rel.is_view:
            self.relation.uncache()

    def join(self, subseq):
        relname = self.relname
        result = []
        assns = self.assns
        if not subseq: return result
        # apply equalities to unitary subseq (embedded subq)
        if len(subseq)==1:
            subseq0 = subseq[0]
            subseq0r = subseq0.remap(self.eqs)
            if subseq0r is None:
                return [] # inconsistent
            subseq0 = subseq0 + subseq0r + assns
            if subseq0.Clean() is None:
                return [] # inconsistent
            subseq = [subseq0]
        rassns = self.rassns
        #print "rassns", rassns
        #print "subseq", subseq
        if rassns is None:
            #print "inconsistent btup"
            return []
        relation = self.relation
        #print "assns", assns
        transformG = self.transformG
        #print "transformG", transformG
        transform = self.transform
        atts = self.atts
        batts = self.batts
        #print "batts, atts", batts, atts
        if not batts:
            #print "cross product", relname
            tuples = relation.rows()
            for t in tuples:
                #print "t is", t
                if rassns:
                    t = (t + rassns).Clean()
                if t is None:
                    #print "preselect fails"
                    continue
                new = t.remap(transformG)
                #print "new is", new
                if new is None:
                    #print "transform fails"
                    continue
                for subst in subseq:
                    #print "subst", subst
                    if subst:
                        add = (subst + new).Clean()
                    else:
                        add = new
                    #print "add is", add
                    if add is not None:
                        result.append(add)
        else:
            # hash join
            #print "hash join"
            # first try to use an index
            index = relation.choose_index(atts)
            #print transform
            if index is not None:
                #print "index join", index.name, relname
                #print index.index.keys()
                #print "rassns", rassns
                atts = index.attributes()
                invtransform = ~transform
                if len(atts)==1:
                    batts = (invtransform[atts[0]],)
                else:
                    batts = invtransform.dump(atts)
                hash_tups = 1
                tindex = index.index
                # memoize converted tuples
                tindex0 = {}
                test = tindex.has_key
                test0 = tindex0.has_key
                for i in xrange(len(subseq)):
                    subst = subseq[i]
                    #print "substs is", subst
                    its = subst.dump(batts)
                    #print "its", its
                    othersubsts = []
                    if test0(its):
                        othersubsts = tindex0[its]
                    elif test(its):
                        tups = tindex[its]
                        for t in tups:
                            #print "t before", t
                            t = (t+rassns).Clean()
                            #print "t after", t
                            if t is None: continue
                            new = t.remap(transformG)
                            #print "new", new
                            if new is None: continue
                            othersubsts.append(new)
                    tindex0[its] = othersubsts
                    for other in othersubsts:
                        #print "adding", other, subst
                        add = (other + subst).Clean()
                        if add is not None:
                            result.append(add)
            # hash join
            #print "hash join"
            else:
                tuples = relation.rows()
                if len(subseq)<len(tuples):
                    #print "hash subseq", relname
                    subseqindex = {}
                    test = subseqindex.has_key
                    for i in xrange(len(subseq)):
                        subst = subseq[i]
                        its = subst.dump(batts)
                        #print "items1", subseq, batts, its
                        if test(its):
                            subseqindex[its].append(subst)
                        else:
                            subseqindex[its] = [ subst ]
                    for t in tuples:
                        #print "on", t
                        if rassns:
                            t = (t+rassns).Clean()
                        if t is None:
                            #print "preselect fails"
                            continue
                        its = t.dump(atts)
                        #print "items2", its
                        if test(its):
                            new = t.remap(transformG)
                            #print "...new", new
                            if new is None:
                                #print "transform fails"
                                continue
                            l = subseqindex[its]
                            for subst in l:
                                add = (subst + new).Clean()
                                #print "adding", add
                                if add is not None:
                                    result.append(add)
                else:
                    #print "hash tuples", relname
                    tindex = {}
                    test = tindex.has_key
                    for i in xrange(len(tuples)):
                        t = tuples[i]
                        if rassns:
                            t = (t + rassns).Clean()
                        if t is None:
                            #print "preselect fails"
                            continue
                        new = t.remap(transformG)
                        #print "new is", new
                        if new is None:
                            #print "transform fails"
                            continue
                        its = t.dump(atts)
                        #print "items3", its
                        if test(its):
                            tindex[its].append(new)
                        else:
                            tindex[its] = [ new ]
                    for subst in subseq:
                        its = subst.dump(batts)
                        #print "items4", its
                        if test(its):
                            n = tindex[its]
                            for new in n:
                                add = (subst + new).Clean()
                                if add is not None:
                                    result.append(add)
        #print "hashjoin result"
        #for x in result:
            #print x
            #break
        return result

### essentially, specialized pickle for this app:

def deserialize(description):
    """simple protocol for generating a marshallable ob"""
    #print "deserialize", description
    from types import TupleType
    if type(description) is not TupleType:
        return description # base type
    try:
        (name, desc) = description
    except:
        return description # base type
    if name == "tuple":
        # tuple case
        return desc
    ### other special cases here...
    if name == "list":
        # list case: map deserialize across desc
        return map(deserialize, desc)
    # all other cases are classes of sqlsem
    import sqlsem
    klass = getattr(sqlsem, name)
    (args1, args2) = desc
    args1 = tuple(map(deserialize, args1))
    ob = apply(klass, args1)
    ob.demarshal(args2)
    return ob

def serialize(ob):
    """dual of deserialize."""
    from types import ListType
    tt = type(ob)
    # for lists map serialize across members
    #if tt is ListType:
    #    return ("list", map(serialize, ob))
    try:
        #print ob.__class__, ob
        args1 = ob.initargs()
        #print "args1 before", args1
        args1 = tuple(map(serialize, args1))
        #print "args1 after", args1
        args2 = ob.marshaldata()
        return (ob.__class__.__name__, (args1, args2))
    except:
        from types import InstanceType
        #tt = type(ob)
        if tt is InstanceType:
            #ext = traceback.extract_tb(sys.exc_traceback)
            #for x in ext:
                #print x
            #print
            #print sys.exc_type, sys.exc_value
            #print ob.__class__
            raise ValueError, "couldn't serialize %s %s" % (
              tt, ob.__class__)
        # assume base type otherwise
        return ob

# invariant:
#   deserialize(serialize(ob)) returns semantic copy of ob
#   serialize(ob) is marshallable
# ie,
#   args1 = ob.initargs() # init args
#   args1d = map(serialize, args1) # serialized
#   args2 = ob.marshaldata() # marshalable addl info
#   # assert args1d, args2 are marshallable
#   args1copy = map(deserialize, args1)
#   ob2 = ob.__class__(args1copy)
#   ob2 = ob2.demarshal(args2)
#   # assert ob2 is semantic copy of ob

class SimpleRecursive:
    """Simple Recursive structure, only requires initargs"""
    def demarshal(self, args):
        pass
    def marshaldata(self):
        return ()

class BoundTuple:

    clean = 1  # false if inconsistent
    closed = 0 # true if equality constraints inferred

    def __init__(self, **bindings):
        """bindings are name>simpletuple associations."""
        self.eqs = Graph()
        self.assns = Tuple()
        for (name, simpletuple) in bindings.items():
            self.bind(name, simpletuple)

    def initargs(self):
        return ()

    def marshaldata(self):
        #print "btp marshaldata", self
        return (self.eqs.items(), self.assns.items(), self.clean, self.closed)

    def demarshal(self, args):
        (eitems, aitems, self.clean, self.closed) = args
        self.eqs = kjbuckets.kjGraph(eitems)
        self.assns = kjbuckets.kjDict(aitems)

    def relbind(self, dict, db):
        """return bindings of self wrt dict rel>att"""
        result = BoundTuple()
        e2 = result.eqs
        a2 = result.assns
        for ((a,b), (c,d)) in self.eqs.items():
            if a is None:
                try:
                    a = dict[b]
                except KeyError:
                    raise NameError, `b`+": ambiguous or unknown attribute"
            if c is None:
                try:
                    c = dict[d]
                except KeyError:
                    raise NameError, `d`+": ambiguous or unknown attribute"
            e2[(a,b)] = (c,d)
        for ((a,b), v) in self.assns.items():
            if a is None:
                try:
                    a = dict[b]
                except KeyError:
                    raise NameError, `b`+": ambiguous or unknown attribute"
            a2[(a,b)] = v
        result.closed = self.closed
        result.clean = self.clean
        return result

    #def known(self, relname):
    #    """return ([(relname, a1), ...], [a1, ...])
    #       for attributes ai of relname known in self."""
    #    atts = []
    #    batts = []
    #    for x in self.assns.keys():
    #        (r,a) = x
    #        if r==relname:
    #           batts.append(x)
    #           atts.append(a)
    #    return (batts, atts)

    def relorder(self, db, allrels):
        """based on known constraints, pick an
           ordering for materializing relations.
           db is database (ignored currently)
           allrels is names of all relations to include (list)."""
        ### not very smart about indices yet!!!
        if len(allrels)<2:
            # doesn't matter
            return allrels
        order = []
        eqs = self.eqs
        assns = self.assns
        kjSet = kjbuckets.kjSet
        kjGraph = kjbuckets.kjGraph
        pinned = kjSet()
        has_index = kjSet()
        needed = kjSet(allrels)
        akeys = assns.keys()
        for (r,a) in akeys:
            pinned[r]=r # pinned if some value known
        known_map = kjGraph(akeys)
        for r in known_map.keys():
            rknown = known_map.neighbors(r)
            if db.has_key(r):
                rel = db[r]
                index = rel.choose_index(rknown)
                if index is not None:
                    has_index[r] = r # has an index!
        if pinned: pinned = pinned & needed
        if has_index: has_index = has_index & needed
        related = kjGraph()
        for ( (r1, a1), (r2, a2) ) in eqs.items():
            related[r1]=r2 # related if equated to other
            related[r2]=r1 # redundant if closed.
        if related: related = needed * related * needed
        chosen = kjSet()
        pr = kjSet(related) & pinned
        # choose first victim
        if has_index:
            choice = has_index.choose_key()
        elif pr:
            choice = pr.choose_key()
        elif pinned:
            choice = pinned.choose_key()
        elif related:
            choice = related.choose_key()
        else:
            return allrels[:] # give up!
        while pinned or related or has_index:
            order.append(choice)
            chosen[choice] = 1
            if pinned.has_key(choice):
                del pinned[choice]
            if related.has_key(choice):
                del related[choice]
            if has_index.has_key(choice):
                del has_index[choice]
            nexts = related * chosen
            if nexts:
                # prefer a relation related to chosen
                choice = nexts.choose_key()
            elif pinned:
                # otherwise one that is pinned
                choice = pinned.choose_key()
            elif related:
                # otherwise one that relates to something...
                choice = related.choose_key()
        others = kjSet(allrels) - chosen
        if others: order = order + others.items()
        return order

    def domain(self):
        kjSet = kjbuckets.kjSet
        return kjSet(self.eqs) + kjSet(self.assns)

    def __repr__(self):
        from string import join
        result = []
        for ( (name, att), value) in self.assns.items():
            result.append( "%s.%s=%s" % (name, att, `value`) )
        for ( (name, att), (name2, att2) ) in self.eqs.items():
            result.append( "%s.%s=%s.%s" % (name, att, name2, att2) )
        if self.clean:
            if not result: return "TRUE"
        else:
            result.insert(0, "FALSE")
        result.sort()
        return join(result, " & ")

    def equate(self, equalities):
        """add equalities to self, only if not closed.
           equalities should be seq of ( (name, att), (name, att) )
           """
        if self.closed: raise ValueError, "cannot add equalities! Closed!"
        e = self.eqs
        for (a, b) in equalities:
            e[a] = b

    def close(self):
        """infer equalities, if consistent.
           only recompute equality closure if not previously closed.
           return None on inconsistency.
        """
        neweqs = self.eqs
        if not self.closed:
            self.eqs = neweqs = (neweqs + ~neweqs).tclosure() # sym, trans closure
            self.closed = 1
        # add trivial equalities to self
        for x in self.assns.keys():
            if not neweqs.member(x,x):
                neweqs[x] = x
        newassns = self.assns.remap(neweqs)
        if newassns is not None and self.clean:
            self.assns = newassns
            #self.clean = 1
            return self
        else:
            self.clean = 0
            return None

    def share_eqs(self):
        """make clone of self that shares equalities, closure.
           note: will share future side effects to eqs too."""
        result = BoundTuple()
        result.eqs = self.eqs
        result.closed = self.closed
        return result

    def __add__(self, other):
        """combine self with other, return closure."""
        result = self.share_eqs()
        se = self.eqs
        oe = other.eqs
        if (se is not oe) and (se != oe):
            result.eqs = se + oe
            result.closed = 0
        ra= result.assns = self.assns + other.assns
        result.clean = result.clean and (ra.Clean() is not None)
        return result.close()

    def __and__(self, other):
        """return closed constraints common to self and other."""
        result = BoundTuple()
        se = self.eqs
        oe = other.eqs
        if (se is oe) or (se == oe):
            result.eqs = self.eqs
            result.closed = self.closed
        else:
            result.eqs = self.eqs & other.eqs
        result.assns = self.assns & other.assns
        result.clean = self.clean and other.clean
        return result.close()

    def __hash__(self):
        # note: equalities don't enter into hash computation!
        # (some may be spurious)
        self.close()
        return hash(self.assns)# ^ hash(self.eqs)

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        sa = self.assns
        oa = other.assns
        test = cmp(sa, oa)
        if test: return test
        kjSet = kjbuckets.kjSet
        kjGraph = kjbuckets.kjSet
        se = self.eqs
        se = kjGraph(se) - kjGraph(kjSet(se))
        oe = other.eqs
        oe = kjGraph(oe) - kjGraph(kjSet(oe))
        return cmp(se, oe)

class BoundExpression(SimpleRecursive):
    """superclass for all bound expressions.
       except where overloaded expressions are binary
       with self.left and self.right
    """

    contains_aggregate = 0 # default

    def __init__(self, left, right):
        self.left = left
        self.right = right
        self.contains_aggregate = left.contains_aggregate or right.contains_aggregate

    def initargs(self):
        return (self.left, self.right)

    def uncache(self):
        """prepare for execution, clear cached data."""
        self.left.uncache()
        self.right.uncache()

    # eventually add converters...
    def equate(self,other):
        """return predicate equating self and other.
           Overload for special cases, please!"""
        return NontrivialEqPred(self, other)

    def attribute(self):
        return (None, `self`)

    def le(self, other):
        """predicate self<=other"""
        return LessEqPred(self, other)

    # these should be overridden for 2 const case someday...
    def lt(self, other):
        """predicate self<other"""
        return LessPred(self, other)

    def __coerce__(self, other):
        return (self, other)

    def __add__(self, other):
        return BoundAddition(self, other)

    def __sub__(self, other):
        return BoundSubtraction(self, other)

    def __mul__(self, other):
        return BoundMultiplication(self, other)

    def __neg__(self):
        return BoundMinus(self)

    def __div__(self, other):
        return BoundDivision(self, other)

    def relbind(self, dict, db):
        Class = self.__class__
        return Class(self.left.relbind(dict, db), self.right.relbind(dict, db))

    def __repr__(self):
        return "(%s)%s(%s)" % (self.left, self.op, self.right)

    def domain(self):
        return self.left.domain() + self.right.domain()
    # always overload value

class BoundMinus(BoundExpression, SimpleRecursive):
    def __init__(self, thing):
        self.thing = thing
        self.contains_aggregate = thing.contains_aggregate
    def initargs(self):
        return (self.thing,)
    def __repr__(self):
        return "-(%s)" % (self.thing,)
    def value(self, contexts):
        from types import IntType
        tt = type
        result = self.thing.value(contexts)
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                result[i] = -result[i]
        return result
    def relbind(self, dict, db):
        Class = self.__class__
        return Class(self.thing.relbind(dict,db))
    def uncache(self):
        self.thing.uncache()
    def domain(self):
        return self.thing.domain()


### stuff for aggregation
class Average(BoundMinus):

    contains_aggregate = 1

    def __init__(self, expr, distinct=0):
        self.distinct = distinct
        if expr.contains_aggregate:
            raise ValueError, `expr`+": aggregate in aggregate "+self.name
        self.thing = expr

    name = "Average"
    def __repr__(self):
        distinct = ""
        if self.distinct:
            distinct = "distinct "
        return "%s(%s%s)" % (self.name, distinct, self.thing)

    def relbind(self, dict, db):
        Class = self.__class__
        return Class(self.thing.relbind(dict,db), self.distinct)

    def value(self, contexts):
        if not contexts: return [] # ???
        test = contexts[0]
        if not test.has_key(None):
            return [self.all_value(contexts)]
        else:
            return self.agg_value(contexts)

    def dvalues(self, values):
        d = {}
        for i in xrange(len(values)):
            d[values[i]] = 1
        return d.keys()

    def all_value(self, contexts):
        thing = self.thing
        values = self.clean(thing.value(contexts), contexts)
        if self.distinct:
            values = self.dvalues(values)
        return self.op(values)

    def clean(self, values, contexts):
        D = {}
        from types import IntType
        tt = type
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                D[i] = values[i]
        return D.values()

    def agg_value(self, contexts):
        from types import IntType
        tt = type
        result = list(contexts)
        for i in xrange(len(contexts)):
            context = contexts[i]
            if tt(context) is not IntType:
                result[i] = self.all_value( context[None] )
        return result

    def op(self, values):
        sum = 0
        for x in values:
            sum = sum+x
        return sum/(len(values)*1.0)

class Sum(Average):
    name = "Sum"
    def op(self, values):
        if not values: return 0
        sum = values[0]
        for x in values[1:]:
            sum = sum+x
        return sum

class Median(Average):
    name = "Median"
    def op(self, values):
        if not values:
            raise ValueError, "Median of empty set"
        values = list(values)
        values.sort()
        lvals = len(values)
        return values[lvals/2]

class Maximum(Average):
    name = "Maximum"
    def op(self, values):
        return max(values)

class Minimum(Average):
    name = "Minimum"
    def op(self, values):
        return min(values)

class Count(Average):
    name = "Count"
    distinct = 0
    def __init__(self, thing, distinct = 0):
        if thing=="*":
            self.thing = "*"
        else:
            Average.__init__(self, thing, distinct)

    def domain(self):
        thing = self.thing
        if thing=="*":
            return kjbuckets.kjSet()
        return thing.domain()

    def all_value(self, contexts):
        thing = self.thing
        if thing=="*" or not self.distinct:
            test = self.clean(contexts, contexts)
            #print "count len", len(test), contexts[0]
            return len(test)
        return Average.all_value(self, contexts)
    def op(self, values):
        return len(values)
    def relbind(self, dict, db):
        if self.thing=="*":
            return self
        return Average.relbind(self, dict, db)
    def uncache(self):
        if self.thing!="*": self.thing.uncache()

def aggregate(assignments, exprlist):
    """aggregates are a assignments with special
       attribute None > list of subtuple"""
    lexprs = len(exprlist)
    if lexprs<1:
        raise ValueError, "aggregate on no expressions?"
    lassns = len(assignments)
    pairs = list(exprlist)
    for i in xrange(lexprs):
        expr = exprlist[i]
        attributes = [expr.attribute()]*lassns
        values = expr.value(assignments)
        pairs[i] = map(None, attributes, values)
    #for x in pairs:
        #print "pairs", x
    if lexprs>1:
        newassnpairs = apply(map, (None,)+tuple(pairs))
    else:
        newassnpairs = pairs[0]
    #for x in newassnpairs:
        #print "newassnpairs", x
    xassns = range(lassns)
    dict = {}
    test = dict.has_key
    for i in xrange(lassns):
        thesepairs = newassnpairs[i]
        thissubassn = assignments[i]
        if test(thesepairs):
            dict[thesepairs].append(thissubassn)
        else:
            dict[thesepairs] = [thissubassn]
    items = dict.items()
    result = list(items)
    kjDict = kjbuckets.kjDict
    if lexprs>1:
        for i in xrange(len(items)):
            (pairs, subassns) = items[i]
            #print "pairs", pairs
            #print "subassns", subassns
            D = kjDict(pairs)
            D[None] = subassns
            result[i] = D
    else:
        for i in xrange(len(items)):
            (pair, subassns) = items[i]
            #print "pair", pair
            #print "subassns", subassns
            result[i] = kjDict( [pair, (None, subassns)] )
    return result

### stuff for order_by
class DescExpr(BoundMinus):
    """special wrapper used only for order by descending
       for things with no -thing operation (eg, strings)"""

    def __init__(self, thing):
        self.thing = thing
        self.contains_aggregate = thing.contains_aggregate

    def value(self, contexts):
        from types import IntType, StringType
        tt = type
        result = self.thing.value(contexts)
        allwrap = None
        allnowrap = None
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                resulti = result[i]
                # currently assume only value needing wrapping is string
                if tt(resulti) is StringType:
                    if allnowrap is not None:
                        raise ValueError, "(%s, %s) cannot order desc" % (allnowrap, resulti)
                    allwrap = resulti
                    result[i] = descOb(resulti)
                else:
                    if allwrap is not None:
                        raise ValueError, "(%s, %s) cannot order desc" % (allwrap, resulti)
                    allnowrap = resulti
                    result[i] = -resulti
        return result
    def __repr__(self):
        return "DescExpr(%s)" % (self.thing,)
    def orderbind(self, order):
        """order is list of (att, expr)."""
        Class = self.__class__
        return Class(self.thing.orderbind(order))

class SimpleColumn(SimpleRecursive):
    """a simple column name for application to a list of tuples"""
    contains_aggregate = 0
    def __init__(self, name):
        self.name = name
    def relbind(self, dict, db):
        # already bound!
        return self
    def orderbind(self, whatever):
        # already bound!
        return self
    def initargs(self):
        return (self.name,)
    def value(self, simpletuples):
        from types import IntType
        tt = type
        name = self.name
        result = list(simpletuples)
        for i in xrange(len(result)):
            ri = result[i]
            if tt(ri) is not IntType:
                result[i] = ri[name]
            else:
                result[i] = None # ???
        return result
    def __repr__(self):
        return "<SimpleColumn %s>" % (self.name,)

class NumberedColumn(BoundMinus):
    """order by column number"""
    contains_aggregate = 0
    def __init__(self, num):
        self.thing = num
    def __repr__(self):
        return "<NumberedColumn %s>" % (self.thing,)
    def relbind(self, dict, db):
        from types import IntType
        if type(self.thing)!=IntType:
            raise ValueError, `self.thing`+": not a numbered column"
        return self
    def orderbind(self, order):
        return SimpleColumn( order[self.thing-1][0] )

class OrderExpr(BoundMinus):
    """order by expression."""
    def orderbind(self, order):
        expratt = self.thing.attribute()
        for (att, exp) in order:
            if exp.attribute()==expratt:
                return SimpleColumn(att)
        else:
            raise NameError, `self`+": invalid ordering specification"
    def __repr__(self):
        return "<order expression %s>" % (self.thing,)

class descOb:

    """special wrapper only used for sorting in descending order
       should only be compared with other descOb instances.
       should only wrap items that cannot be easily "order inverted",
       (eg, strings).
    """

    def __init__(self, ob):
        self.ob = ob

    def __cmp__(self, other):
        #test = cmp(self.__class__, other.__class__)
        #if test: return test
        return -cmp(self.ob,other.ob)

    def __coerce__(self, other):
        return (self, other)

    def __hash__(self):
        return hash(self.ob)

    def __repr__(self):
        return "descOb(%s)" % (self.ob,)

def PositionedSort(num, ord):
    nc = NumberedColumn(num)
    if ord=="DESC":
        return DescExpr(nc)
    return nc

def NamedSort(name, ord):
    oe = OrderExpr(name)
    if ord=="DESC":
        return DescExpr(oe)
    return oe

def relbind_sequence(order_list, dict, db):
    result = list(order_list)
    for i in xrange(len(order_list)):
        result[i] = result[i].relbind(dict,db)
    return result

def orderbind_sequence(order_list, order):
    result = list(order_list)
    for i in xrange(len(order_list)):
        result[i] = result[i].orderbind(order)
    return result

def order_tuples(order_list, tuples):
    lorder_list = len(order_list)
    ltuples = len(tuples)
    if lorder_list<1:
        raise ValueError, "order on empty list?"
    order_map = list(order_list)
    for i in xrange(lorder_list):
        order_map[i] = order_list[i].value(tuples)
    if len(order_map)>1:
        order_vector = apply(map, (None,)+tuple(order_map) )
    else:
        order_vector = order_map[0]
    #G = kjbuckets.kjGraph()
    pairs = map(None, range(ltuples), tuples)
    ppairs = map(None, order_vector, pairs)
    G = kjbuckets.kjGraph(ppairs)
    #for i in xrange(ltuples):
    #    G[ order_vector[i] ] = (i, tuples[i])
    Gkeys = G.keys()
    Gkeys.sort()
    result = list(tuples)
    index = 0
    for x in Gkeys:
        #print x
        for (i, y) in G.neighbors(x):
            #print "   ", y
            result[index]=y
            index = index+1
    if index!=ltuples:
        raise ValueError, \
         "TUPLE LOST IN ORDERING COMPUTATION! (%s,%s)" % (ltuples, index)
    return result

class BoundAddition(BoundExpression):
    """promised addition."""
    op = "+"
    def value(self, contexts):
        from types import IntType
        tt = type
        lvs = self.left.value(contexts)
        rvs = self.right.value(contexts)
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                lvs[i] = lvs[i] + rvs[i]
        return lvs

class BoundSubtraction(BoundExpression):
    """promised subtraction."""
    op = "-"
    def value(self, contexts):
        from types import IntType
        tt = type
        lvs = self.left.value(contexts)
        rvs = self.right.value(contexts)
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                lvs[i] = lvs[i] - rvs[i]
        return lvs

class BoundMultiplication(BoundExpression):
    """promised multiplication."""
    op = "*"
    def value(self, contexts):
        from types import IntType
        tt = type
        lvs = self.left.value(contexts)
        rvs = self.right.value(contexts)
        #print lvs
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                lvs[i] = lvs[i] * rvs[i]
        return lvs

class BoundDivision(BoundExpression):
    """promised division."""
    op = "/"
    def value(self, contexts):
        from types import IntType
        tt = type
        lvs = self.left.value(contexts)
        rvs = self.right.value(contexts)
        for i in xrange(len(contexts)):
            if tt(contexts[i]) is not IntType:
                lvs[i] = lvs[i] / rvs[i]
        return lvs

class BoundAttribute(BoundExpression):
    """bound attribute: initialize with relname=None if
       implicit."""

    contains_aggregate = 0

    def __init__(self, rel, name):
        self.rel = rel
        self.name = name

    def initargs(self):
        return (self.rel, self.name)

    def relbind(self, dict, db):
        if self.rel is not None: return self
        name = self.name
        try:
            rel = dict[name]
        except KeyError:
            raise NameError, `name` + ": unknown or ambiguous"
        return BoundAttribute(rel, name)

    def uncache(self):
        pass

    def __repr__(self):
        return "%s.%s" % (self.rel, self.name)

    def attribute(self):
        """return (rename, attribute) for self."""
        return (self.rel, self.name)

    def domain(self):
        return kjbuckets.kjSet([ (self.rel, self.name) ])

    def value(self, contexts):
        """return value of self in context (bound tuple)."""
        #print "value of ", self, "in", contexts
        from types import IntType
        tt = type
        result = list(contexts)
        ra = (self.rel, self.name)
        for i in xrange(len(result)):
            if tt(result[i]) is not IntType:
                result[i] = contexts[i][ra]
        return result

    def equate(self, other):
        oc = other.__class__
        if oc==BoundAttribute:
            result = BoundTuple()
            result.equate([(self.attribute(), other.attribute())])
            return BTPredicate(result)
        elif oc==Constant:
            result = BoundTuple()
            result.assns[ self.attribute() ] = other.value([1])[0]
            return BTPredicate(result)
        else:
            return NontrivialEqPred(self, other)

class Constant(BoundExpression):
    contains_aggregate = 0

    def __init__(self, value):
        self.value0 = value

    def __hash__(self):
        return hash(self.value0)

    def initargs(self):
        return (self.value0,)

    def domain(self):
        return kjbuckets.kjSet()

    def __add__(self, other):
        if other.__class__==Constant:
            return Constant(self.value0 + other.value0)
        return BoundAddition(self, other)

    def __sub__(self, other):
        if other.__class__==Constant:
            return Constant(self.value0 - other.value0)
        return BoundSubtraction(self, other)

    def __mul__(self, other):
        if other.__class__==Constant:
            return Constant(self.value0 * other.value0)
        return BoundMultiplication(self, other)

    def __neg__(self):
        return Constant(-self.value0)

    def __div__(self, other):
        if other.__class__==Constant:
            return Constant(self.value0 / other.value0)
        return BoundDivision(self, other)

    def relbind(self, dict, db):
        return self

    def uncache(self):
        pass

    def value(self, contexts):
        """return the constant value associated with self."""
        return [self.value0] * len(contexts)

    def equate(self,other):
        if other.__class__==Constant:
            if other.value0 == self.value0:
                return BTPredicate() #true
            else:
                return ~BTPredicate() #false
        else:
            return other.equate(self)

    def attribute(self):
        """invent a pair to identify a constant"""
        return ('unbound', `self`)

    def __repr__(self):
        return "<const %s at %s>" % (`self.value0`, id(self))

class TupleCollector:
    """Translate a sequence of assignments to simple tuples.
       (for implementing the select list of a SELECT).
    """
    contains_aggregate = 0
    contains_nonaggregate = 0

    def __init__(self):
        self.final = None
        self.order = []
        self.attorder = []
        self.exporder = []

    def initargs(self):
        return ()

    def marshaldata(self):
        exps = map(serialize, self.exporder)
        return (self.attorder, exps,
                self.contains_aggregate, self.contains_nonaggregate)

    def demarshal(self, args):
        (self.attorder, exps, self.contains_aggregate,
         self.contains_nonaggregate) = args
        exporder = self.exporder = map(deserialize, exps)
        self.order = map(None, self.attorder, exporder)

    def uncache(self):
        for exp in self.exporder:
            exp.uncache()

    def domain(self):
        all=[]
        for e in self.exporder:
            all = all+e.domain().items()
        return kjbuckets.kjSet(all)

    def __repr__(self):
        l = []
        for (att, exp) in self.order:
            l.append( "%s as %s" % (exp, att) )
        from string import join
        return join(l, ", ")

    def addbinding(self, attribute, expression):
        """bind att>expression."""
        self.order.append((attribute, expression) )
        self.attorder.append(attribute )
        self.exporder.append(expression)
        if expression.contains_aggregate:
            self.contains_aggregate = 1
        else:
            self.contains_nonaggregate = 1

    def map(self, assnlist):
        """remap btlist by self. return (tuplelist, attorder)"""
        # DON'T eliminate nulls
        from types import IntType
        tt = type
        values = []
        for exp in self.exporder:
            values.append(exp.value(assnlist))
        if len(values)>1:
            valtups = apply(map, (None,) + tuple(values) )
        else:
            valtups = values[0]
        kjUndump = kjbuckets.kjUndump
        undumper = tuple(self.attorder)
        for i in xrange(len(valtups)):
            test = assnlist[i]
            if tt(test) is IntType or test is None:
                valtups[i] = 0 # null/false
            else:
                tup = valtups[i]
                valtups[i] = kjUndump(undumper, tup)
        return (valtups, self.attorder)

    def relbind(self, dict, db):
        """disambiguate missing rel names if possible.
           also choose output names appropriately."""
        # CURRENTLY THIS IS AN "IN PLACE" OPERATION
        order = self.order
        attorder = self.attorder
        exporder = self.exporder
        known = {}
        for i in xrange(len(order)):
            (att, exp) = order[i]
            #print exp
            exp = exp.relbind(dict, db)
            if att is None:
                # choose a name for this column
                #print exp
                (rname, aname) = exp.attribute()
                if known.has_key(aname):
                    both = rname+"."+aname
                    att = both
                    count = 0
                    while known.has_key(att):
                        # crank away!
                        count = count+1
                        att = both+"."+`count`
                else:
                    att = aname
            else:
                if known.has_key(att):
                    raise NameError, `att`+" ambiguous in select list"
            order[i] = (att, exp)
            exporder[i] = exp
            attorder[i] = att
            known[att] = att
        return self

class BTPredicate(SimpleRecursive):
    """superclass for bound tuple predicates.
       Eventually should be modified to use "compile" for speed
       to generate an "inlined" evaluation function.
       self(bt) returns bt with additional equality constraints
       (possible) or None if predicate fails."""

    false = 0
    constraints = None
    contains_aggregate = 0

    def __init__(self, constraints=None):
        """default interpretation: True."""
        if constraints is not None:
            self.constraints = constraints.close()

    def initargs(self):
        return (self.constraints,)

    def relbind(self, dict, db):
        c = self.constraints
        if c is None: return self
        return BTPredicate( self.constraints.relbind(dict, db) )

    def uncache(self):
        pass

    #def evaluate(self, db, relnames):
        #"""evaluate the predicate over database bindings."""
        # pretty simple strategy right now...
        ### need to do something about all/distinct...
        #c = self.constraints
        #if c is None:
        #  c = BoundTuple()
        #order = c.relorder(db, relnames)
        #if not order:
        #   raise ValueError, "attempt to evaluate over no relations: "+`relnames`
        #result = [c]
        #for r in order:
        #    result = hashjoin(result, r, db[r])
        #if self.__class__==BTPredicate:
        #   # if it's just equality conjunction, we're done
        #   return result
        #else:
        #   # apply additional constraints
        #   return self(result)

    def domain(self):
        c = self.constraints
        kjSet = kjbuckets.kjSet
        if c is None: return kjSet()
        return c.domain()

    def __repr__(self):
        if self.false: return "FALSE"
        c = self.constraints
        if c is None: c = "true"
        return "[pred](%s)" % c

    def detrivialize(self):
        """hook added to allow elimination of trivialities
           return None if completely true, or simpler form
           or self, if no simplification is possible."""
        if self.false: return self
        if not self.constraints: return None
        return self

    def negated_constraints(self):
        """equality constraints always false of satisfactory tuple."""
        return BoundTuple() # there aren't any

    def __call__(self, assignments, toplevel=0):
        """apply self to sequence of assignments
           return copy of asssignments with false results
           replaced by 0!  Input may have 0's!"""
        # optimization
        #   if toplevel, the btpred has been evaluated during join.
        if toplevel:
            return list(assignments)
        from types import IntType
        tt = type
        lbt = len(assignments)
        if self.false: return [0] * lbt
        c = self.constraints
        if c is None or not c:
            result = assignments[:] # no constraints
        else:
            assns = c.assns
            eqs = c.eqs
            eqsinteresting = 0
            for (a,b) in eqs.items():
                if a!=b:
                    eqsinteresting = 1
            result = assignments[:]
            for i in xrange(lbt):
                this = assignments[i]
                #print "comparing", self, "to", this
                if type(this) is IntType: continue
                this = (this + assns).Clean()
                if this is None:
                    result[i] = 0
                elif eqsinteresting:
                    this = this.remap(eqs)
                    if this is None:
                        result[i] = 0
        return result

    def __and__(self, other):
        """NOTE: all subclasses must define an __and__!!!"""
        #print "BTPredicate.__and__", (self, other)
        if self.__class__==BTPredicate and other.__class__==BTPredicate:
            c = self.constraints
            o = other.constraints
            if c is None: return other
            if o is None: return self
            if self.false: return self
            if other.false: return other
            # optimization for simple constraints
            all = (c+o)
            result = BTPredicate( all ) # all constraints
            if all is None: result.false = 1
        else:
            result = other & self
        return result

    def __or__(self, other):
        if self.__class__==BTPredicate and other.__class__==BTPredicate:
            c = self.constraints
            o = other.constraints
            if c is None: return self # true dominates
            if o is None: return other
            if other.false: return self
            if self.false: return other
            if self == other: return self
        result = BTor_pred([self, other])
        return result

    def __invert__(self):
        if self.false:
            return BTPredicate()
        if not self.constraints:
            result = BTPredicate()
            result.false = 1
            return result
        return BTnot_pred(self)

    def __cmp__(self, other):
        test = cmp(other.__class__, self.__class__)
        if test: return test
        if self.false and other.false: return 0
        return cmp(self.constraints, other.constraints)

    def __hash__(self):
        if self.false: return 11111
        return hash(self.constraints)

class BTor_pred(BTPredicate):

    def __init__(self, members, *othermembers):
        # replace any OR in members with its members
        #print "BTor_pred", members
        members = list(members) + list(othermembers)
        for m in members[:]:
            if m.__class__==BTor_pred:
                members.remove(m)
                members = members + m.members
        #print "before", members
        members = self.members = kjbuckets.kjSet(members).items()
        #print members
        for m in members[:]:
            if m.false: members.remove(m)
        self.constraints = None # common constraints
        for m in members:
            if m.contains_aggregate:
                self.contains_aggregate = 1
        if members:
            # common constraints are those in all members
            constraints = members[0].constraints
            for m in members[1:]:
                mc = m.constraints
                if not constraints or not mc:
                    constraints = None
                    break
                constraints = constraints & mc
            self.constraints = constraints
        #print members

    def initargs(self):
        return ((),) + tuple(self.members)

    def relbind(self, dict, db):
        ms = []
        for m in self.members:
            ms.append( m.relbind(dict, db) )
        return BTor_pred(ms)

    def uncache(self):
        for m in self.members:
            m.uncache()

    def domain(self):
        all = BTPredicate.domain(self).items()
        for x in self.members:
            all = all + x.domain().items()
        return kjbuckets.kjSet(all)

    def __repr__(self):
        c = self.constraints
        m = self.members
        mr = map(repr, m)
        from string import join
        mr.sort()
        mr = join(mr, " | ")
        if not mr: mr = "FALSE_OR"
        if c:
            mr = "[disj](%s and %s)" % (c, mr)
        return mr

    def detrivialize(self):
        """hook added to allow elimination of trivialities
           return None if completely true, or simpler form
           or self, if no simplification is possible."""
        ms = self.members
        for i in xrange(len(ms)):
            ms[i] = ms[i].detrivialize()
        # now suck out subordinate ors
        someor = None
        for m in ms:
            if m.__class__== BTor_pred:
                someor = m
                ms.remove(m)
                break
        if someor is not None:
            result = someor
            for m in ms:
                result = result + m
            return result.detrivialize()
        allfalse = 1
        for m in ms:
            if m is None: allfalse=0; break # true member
            allfalse = allfalse & m.false
        if allfalse: return ~BTPredicate() # boundary case
        ms[:] = filter(None, ms)
        if not ms: return None # all true.
        ms[:] = kjbuckets.kjSet(ms).items()
        if len(ms)==1: return ms[0] # or of 1
        return self

    def __call__(self, boundtuples, toplevel=0):
        # apply common constraints first
        lbt = len(boundtuples)
        # boundary case for or is false
        members = self.members
        if not members:
            return [0] * lbt
        current = BTPredicate.__call__(self, boundtuples, toplevel)
        # now apply first alternative
        alt1 = members[0](current)
        # determine questionables
        questionables = current[:]
        rng = xrange(len(current))
        from types import IntType
        tt = type
        for i in rng:
            if tt(alt1[i]) is not IntType:
                questionables[i]=0
        # now test other alternatives
        #print "alt1", members[0]
        #for x in alt1:
            #print x
        for m in self.members[1:]:
            #print "questionables", m
            #for x in questionables:
                #print x
            passm = m(questionables)
            for i in rng:
                test = passm[i]
                if tt(test) is not IntType:
                    questionables[i] = 0
                    alt1[i] = test
        return alt1

    def negated_constraints(self):
        """the negated constraints of an OR are
           the negated constraints of *all* members"""
        ms = self.members
        result = ms.negated_constraints()
        for m in ms[1:]:
            if not result: return result
            mc = m.negated_constraints()
            if not mc: return mc
            result = result & mc
        return result

    def __and__(self, other):
        """push "and" down"""
        newmembers = self.members[:]
        for i in xrange(len(newmembers)):
            newmembers[i] = newmembers[i] & other
        return BTor_pred(newmembers)

    def __or__(self, other):
        """collapse two ors, otherwise just add new member"""
        if self.__class__==BTor_pred and other.__class__==BTor_pred:
            return BTor_pred(self.members+other.members)
        return BTor_pred(self.members + [other])

    def __invert__(self):
        """translate to and-not"""
        ms = self.members
        if not ms: return BTPredicate() # boundary case
        result = ~ms[0]
        for m in ms[1:]:
            result = result & ~m
        return result

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        kjSet = kjbuckets.kjSet
        test = cmp(kjSet(self.members), kjSet(other.members))
        if test: return test
        return BTPredicate.__cmp__(self, other)

    def __hash__(self):
        return hash(kjbuckets.kjSet(self.members))

class BTnot_pred(BTPredicate):

    def __init__(self, thing):
        self.negated = thing
        self.contains_aggregate = thing.contains_aggregate
        self.constraints = thing.negated_constraints()

    def initargs(self):
        return (self.negated,)

    def relbind(self, dict, db):
        return BTnot_pred( self.negated.relbind(dict, db) )

    def uncache(self):
        self.negated.uncache()

    def domain(self):
        result = BTPredicate.domain(self) + self.negated.domain()
        #print "neg domain is", `self`, result
        return result

    def __repr__(self):
        c = self.constraints
        n = self.negated
        r = "(NOT %s)" % n
        if c: r = "[neg](%s & %s)" % (c, r)
        return r

    def detrivialize(self):
        """hook added to allow elimination of trivialities
           return None if completely true, or simpler form
           or self, if no simplification is possible."""
        # first, fix or/and/not precedence
        thing = self.negated
        if thing.__class__ == BTnot_pred:
            return thing.negated.detrivialize()
        if thing.__class__ == BTor_pred:
            # translate to and_not
            members = thing.members[:]
            for i in xrange(len(members)):
                members[i] = ~members[i]
            result = BTand_pred(members)
            return result.detrivialize()
        if thing.__class__ == BTand_pred:
            # translate to or_not
            members = thing.members[:]
            c = thing.constraints # precondition
            if c is not None:
                members.append(BTPredicate(c))
            for i in xrange(len(members)):
                members[i] = ~members[i]
            result = BTor_pred(members)
            return result.detrivialize()
        self.negated = thing = self.negated.detrivialize()
        if thing is None: return ~BTPredicate() # uniquely false
        if thing.false: return None # uniquely true
        return self

    def __call__(self, boundtuples, toplevel=0):
        from types import IntType
        tt = type
        current = BTPredicate.__call__(self, boundtuples, toplevel)
        omit = self.negated(current)
        for i in xrange(len(current)):
            if tt(omit[i]) is not IntType:
                current[i]=0
        return current

    def negated_constraints(self):
        """the negated constraints of a NOT are the
           negated constraints of the thing negated."""
        return self.negated.constraints

    def __and__(self, other):
        """do the obvious thing."""
        return BTand_pred([self, other])

    def __or__(self, other):
        """do the obvious thing"""
        return BTor_pred([self, other])

    def __invert__(self):
        return self.negated

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        test = cmp(self.negated,other.negated)
        if test: return test
        return BTPredicate.__cmp__(self,other)

    def __hash__(self):
        return hash(self.negated)^787876^hash(self.constraints)

class BTand_pred(BTPredicate):

    def __init__(self, members, precondition=None, *othermembers):
        #print "BTand_pred", (members, precondition)
        members = list(members) + list(othermembers)
        members = self.members = kjbuckets.kjSet(members).items()
        self.constraints = precondition # common constraints
        if members:
            # common constraints are those in any member
            if precondition is not None:
                constraints = precondition
            else:
                constraints = BoundTuple()
            for i in xrange(len(members)):
                m = members[i]
                mc = m.constraints
                if mc:
                    #print "constraints", constraints
                    constraints = constraints + mc
                    if constraints is None: break
                if m.__class__==BTPredicate:
                    members[i] = None # subsumed above
            members = self.members = filter(None, members)
            for m in members:
                if m.contains_aggregate:
                    self.contains_aggregate=1
            ### consider propagating constraints down?
            self.constraints = constraints
            if constraints is None: self.false = 1

    def initargs(self):
        #print "self.members", self.members
        #print "self.constraints", self.constraints
        #return (list(self.members), self.constraints)
        return ((), self.constraints) + tuple(self.members)

    def relbind(self, dict, db):
        ms = []
        for m in self.members:
            ms.append( m.relbind(dict, db) )
        c = self.constraints.relbind(dict, db)
        return BTand_pred(ms, c)

    def uncache(self):
        for m in self.members:
            m.uncache()

    def domain(self):
        all = BTPredicate.domain(self).items()
        for x in self.members:
            all = all + x.domain().items()
        return kjbuckets.kjSet(all)

    def __repr__(self):
        m = self.members
        c = self.constraints
        r = map(repr, m)
        if self.false: r.insert(0, "FALSE")
        from string import join
        r = join(r, " AND ")
        r = "(%s)" % r
        if c: r = "[conj](%s and %s)" % (c, r)
        return r

    def detrivialize(self):
        """hook added to allow elimination of trivialities
           return None if completely true, or simpler form
           or self, if no simplification is possible."""
        # first apply demorgan's law to push ands down
        # (exponential in worst case).
        #print "detrivialize"
        #print self
        ms = self.members
        some_or = None
        c = self.constraints
        for m in ms:
            if m.__class__==BTor_pred:
                some_or = m
                ms.remove(m)
                break
        if some_or is not None:
            result = some_or
            if c is not None:
                some_or = some_or & BTPredicate(c)
            for m in ms:
                result = result & m # preserves or/and precedence
            if result.__class__!=BTor_pred:
                raise "what the?"
            result = result.detrivialize()
            #print "or detected, returning"
            #print result
            return result
        for i in xrange(len(ms)):
            ms[i] = ms[i].detrivialize()
        ms[:] = filter(None, ms)
        if not ms:
            #print "returning boundary case of condition"
            if c is None:
                return None
            else:
                return BTPredicate(c).detrivialize()
        ms[:] = kjbuckets.kjSet(ms).items()
        if len(ms)==1 and c is None:
            #print "and of 1, returning"
            #print ms[0]
            return ms[0] # and of 1
        return self

    def __call__(self, boundtuples, toplevel=0):
        # apply common constraints first
        current = BTPredicate.__call__(self, boundtuples, toplevel)
        for m in self.members:
            current = m(current)
        return current

    def negated_constraints(self):
        """the negated constraints of an AND are
           the negated constraints of *any* member"""
        ms = self.members
        result = BoundTuple()
        for m in ms:
            mc = m.negated_constraints()
            if mc: result = result + mc
        return result

    def __and__(self, other):
        """push "and" down if other is an or"""
        if other.__class__==BTor_pred:
            return other & self
        c = self.constraints
        # merge in other and
        if other.__class__==BTand_pred:
            allmem = self.members+other.members
            oc = other.constraints
            if c is None:
                c = oc
            elif oc is not None:
                c = c+oc
            return BTand_pred(allmem, c)
        return BTand_pred(self.members + [other], c)

    def __or__(self, other):
        """do the obvious thing."""
        return BTor_pred([self, other])

    def __invert__(self):
        """translate to or-not"""
        ms = self.members
        if not ms: return ~BTPredicate() # boundary case
        result = ~ms[0]
        for m in ms[1:]:
            result = result | ~m
        return result

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        kjSet = kjbuckets.kjSet
        test = cmp(kjSet(self.members), kjSet(other.members))
        if test: return test
        return BTPredicate.__cmp__(self, other)

    def __hash__(self):
        return hash(kjbuckets.kjSet(self.members))

class NontrivialEqPred(BTPredicate):
    """equation of nontrivial expressions."""

    def __init__(self, left, right):
        #print "making pred", self.__class__, left, right
        # maybe should used reflexivity...
        self.left = left
        self.right = right
        self.contains_aggregate = left.contains_aggregate or right.contains_aggregate

    def initargs(self):
        return (self.left, self.right)

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        test = cmp(self.right, other.right)
        if test: return test
        return cmp(other.left, other.left)

    def hash(self, other):
        return hash(self.left) ^ hash(self.right)

    def relbind(self, dict, db):
        Class = self.__class__
        return Class(self.left.relbind(dict,db), self.right.relbind(dict,db) )

    def uncache(self):
        self.left.uncache()
        self.right.uncache()

    def domain(self):
        return self.left.domain() + self.right.domain()

    op = "=="

    def __repr__(self):
        return "(%s)%s(%s)" % (self.left, self.op, self.right)

    def detrivialize(self):
        return self

    def __call__(self, assigns, toplevel=0):
        from types import IntType
        tt = type
        lv = self.left.value(assigns)
        rv = self.right.value(assigns)
        result = assigns[:]
        for i in xrange(len(assigns)):
            t = assigns[i]
            if type(t) is not IntType and lv[i]!=rv[i]:
                result[i] = 0
        return result

    def negated_constraints(self):
        return None

    def __and__(self, other):
        return BTand_pred( [self, other] )

    def __or__(self, other):
        return BTor_pred( [self, other] )

    def __invert__(self):
        return BTnot_pred(self)

class BetweenPredicate(NontrivialEqPred):
    """e1 BETWEEN e2 AND e3"""
    def __init__(self, middle, lower, upper):
        self.middle = middle
        self.lower = lower
        self.upper = upper

    def initargs(self):
        return (self.middle, self.lower, self.upper)

    def domain(self):
        return (
    self.middle.domain() + self.lower.domain() + self.upper.domain())

    def relbind(self, dict, db):
        self.middle = self.middle.relbind(dict, db)
        self.lower = self.lower.relbind(dict, db)
        self.upper = self.upper.relbind(dict, db)
        return self

    def uncache(self):
        self.middle.uncache()
        self.upper.uncache()
        self.lower.uncache()

    def __repr__(self):
        return "(%s BETWEEN %s AND %s)" % (
         self.middle, self.lower, self.upper)

    def __hash__(self):
        return hash(self.middle)^~hash(self.lower)^hash(self.upper)^55

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        test = cmp(self.lower, other.lower)
        if test: return test
        test = cmp(self.middle, other.middle)
        if test: return test
        return cmp(self.upper, other.upper)

    def __call__(self, assigns, toplevel=0):
        from types import IntType
        tt = type
        lowv = self.lower.value(assigns)
        upv = self.upper.value(assigns)
        midv = self.middle.value(assigns)
        result = assigns[:]
        for i in xrange(len(assigns)):
            t = assigns[i]
            if tt(t) is not IntType:
                midvi = midv[i]
                if lowv[i]>midvi or upv[i]<midvi:
                    result[i] = 0
        return result

class ExistsPred(NontrivialEqPred):
    """EXISTS subquery."""
    contains_aggregate = 0

    def __init__(self, subq):
        self.cached_result = None
        self.cachable = None
        self.subq = subq

    def initargs(self):
        return (self.subq,)

    def domain(self):
        result = self.subq.unbound()
        # if there are no outer bindings, evaluate ONCE!
        if not result:
            self.cachable = 1
        return result

    def relbind(self, dict, db):
        self.subq = self.subq.relbind(db, dict)
        return self

    def uncache(self):
        self.cached_result = None
        self.subq.uncache()

    def __repr__(self):
        return "\nEXISTS\n%s\n" % (self.subq,)

    def __call__(self, assigns, toplevel=0):
        ### should optimize!!!
        #print "exists"
        #print self.subq
        from types import IntType
        tt = type
        eval = self.subq.eval
        result = assigns[:]
        # shortcut: if cachable, eval only once and cache
        if self.cachable:
            test = self.cached_result
            if test is None:
                self.cached_result = test = eval()
            #print "exists cached", self.cached_result
            if test:
                return result
            else:
                return [0] * len(result)
        kjDict = kjbuckets.kjDict
        for i in xrange(len(assigns)):
            #print "exists uncached"
            assignsi = assigns[i]
            if tt(assignsi) is IntType: continue
            testbtup = BoundTuple()
            testbtup.assns = kjDict(assignsi)
            test = eval(outerboundtuple=testbtup).rows()
            #for x in test:
                #print "exists for", assignsi
                #print x
                #break
            if not test:
                result[i] = 0
        return result

    def __hash__(self):
        return hash(self.subq)^3333

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        return cmp(self.subq, other.subq)

class QuantEQ(NontrivialEqPred):
    """Quantified equal any predicate"""

    def __init__(self, expr, subq):
        self.expr = expr
        self.subq = subq
        self.cachable = 0
        self.cached_column = None
        self.att = None

    def initargs(self):
        return (self.expr, self.subq)

    def uncache(self):
        self.cached_column = None

    def domain(self):
        first = self.subq.unbound()
        if not first:
            self.cachable = 1
        more = self.expr.domain()
        return first + more

    def relbind(self, dict, db):
        subq = self.subq = self.subq.relbind(db, dict)
        self.expr = self.expr.relbind(dict, db)
        # test that subquery is single column and determine att
        sl = subq.select_list
        atts = sl.attorder
        if len(atts)<>1:
            raise ValueError, \
              "Quantified predicate requires unit select list: %s" % atts
        self.att = atts[0]
        return self

    fmt = "(%s %s ANY %s)"
    op = "="

    def __repr__(self):
        return self.fmt % (self.expr, self.op, self.subq)

    def __call__(self, assigns, toplevel=0):
        cached_column = self.cached_column
        cachable = self.cachable
        expr = self.expr
        subq = self.subq
        att = self.att
        if cachable:
            if cached_column is None:
                subqr = subq.eval().rows()
                cc = self.cached_column = dump_single_column(subqr, att)
            #print self, "cached", self.cached_column
        exprvals = expr.value(assigns)
        kjDict = kjbuckets.kjDict
        compare = self.compare
        tt = type
        from types import IntType
        result = assigns[:]
        for i in xrange(len(assigns)):
            assignsi = assigns[i]
            if tt(assignsi) is IntType: continue
            thisval = exprvals[i]
            testbtup = BoundTuple()
            testbtup.assns = kjDict(assignsi)
            if not cachable:
                subqr = subq.eval(outerboundtuple=testbtup).rows()
                cc = dump_single_column(subqr, att)
                #print self, "uncached", cc, thisval
            if not compare(thisval, cc):
                #print "eliminated", assignsi
                result[i] = 0
        return result

    def compare(self, value, column):
        return value in column

    def __hash__(self):
        return hash(self.subq) ^ ~hash(self.expr)

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        test = cmp(self.expr, other.expr)
        if test: return test
        return cmp(self.subq, other.subq)

# "expr IN (subq)" same as "expr = ANY (subq)"
InPredicate = QuantEQ

class InLits(NontrivialEqPred):
    """expr IN literals, support dynamic bindings."""
    def __init__(self, expr, lits):
        self.expr = expr
        self.lits = lits
        self.cached_lits = None

    def initargs(self):
        return (self.expr, self.lits)

    def uncache(self):
        self.cached_lits = None

    def domain(self):
        d = []
        for l in self.lits:
            d0 = l.domain()
            if d0:
                d = d + d0.items()
        d0 = self.expr.domain()
        if d:
            kjSet = kjbuckets.kjSet
            return d0 + kjSet(d)
        else:
            return d0

    def relbind(self, dict, db):
        newlits = []
        for l in self.lits:
            newlits.append(l.relbind(dict, db))
        self.lits = newlits
        self.expr = self.expr.relbind(dict, db)
        return self

    fmt = "(%s IN %s)"

    def __repr__(self):
        return self.fmt % (self.expr, self.lits)

    def __call__(self, assigns, toplevel=0):
        # LITERALS ARE CONSTANT! NEED ONLY LOOK FOR ONE ASSIGN.
        tt = type
        from types import IntType
        litvals = self.cached_lits
        if litvals is None:
            assigns0 = []
            for asn in assigns:
                if tt(asn) is not IntType:
                    assigns0.append(asn)
                    break
            if not assigns0:
                # all false/unknown
                return assigns
            litvals = []
            for lit in self.lits:
                value = lit.value(assigns0)
                litvals.append(value[0])
            self.cached_lits = litvals
        expr = self.expr
        exprvals = expr.value(assigns)
        result = assigns[:]
        for i in xrange(len(assigns)):
            assignsi = assigns[i]
            if tt(assignsi) is IntType: continue
            thisval = exprvals[i]
            if thisval not in litvals:
                #print "eliminated", assignsi
                result[i] = 0
        return result

    def compare(self, value, column):
        return value in column

    def __hash__(self):
        return 10 ^ hash(self.expr)

    def __cmp__(self, other):
        test = cmp(self.__class__, other.__class__)
        if test: return test
        test = cmp(self.expr, other.expr)
        if test: return test
        return cmp(self.lits, other.lits)

class QuantNE(QuantEQ):
    """Quantified not equal any predicate"""
    op = "<>"
    def compare(self, value, column):
        for x in column:
            if value!=x: return 1
        return 0

### note: faster NOT IN using QuantNE?

class QuantLT(QuantEQ):
    """Quantified less than any predicate"""
    op = "<"
    def uncache(self):
        self.testval = self.cached = self.cached_column = None
    def compare(self, value, column):
        if self.cachable:
            if self.cached:
                testval = self.testval
            else:
                testval = self.testval = max(column)
                self.cached = 1
        else:
            testval = max(column)
        return value < testval

class QuantLE(QuantLT):
    """Quantified less equal any predicate"""
    op = "<="
    def compare(self, value, column):
        if self.cachable:
            if self.cached:
                testval = self.testval
            else:
                testval = self.testval = max(column)
                self.cached = 1
        else:
            testval = max(column)
        return value <= testval

class QuantGE(QuantLT):
    """Quantified greater equal any predicate"""
    op = ">="
    def compare(self, value, column):
        if self.cachable:
            if self.cached:
                testval = self.testval
            else:
                testval = self.testval = min(column)
                self.cached = 1
        else:
            testval = min(column)
        return value >= testval

class QuantGT(QuantLT):
    """Quantified greater than any predicate"""
    op = ">"
    def compare(self, value, column):
        if self.cachable:
            if self.cached:
                testval = self.testval
            else:
                self.testval = testval = min(column)
                self.cached = 1
        else:
            testval = min(column)
        return value > testval

def dump_single_column(assigns, att):
    """dump single column assignment"""
    result = assigns[:]
    for i in xrange(len(result)):
        result[i] = result[i][att]
    return result

class LessPred(NontrivialEqPred):
    op = "<"
    def __call__(self, assigns, toplevel=0):
        from types import IntType
        tt = type
        lv = self.left.value(assigns)
        rv = self.right.value(assigns)
        result = assigns[:]
        for i in xrange(len(assigns)):
            t = assigns[i]
            if tt(t) is not IntType and lv[i]>=rv[i]:
                result[i] = 0
        return result

    def __inv__(self):
        return LessEqPred(self.right, self.left)

    def __hash__(self):
        return hash(self.left)^hash(self.right)


class LessEqPred(LessPred):
    op = "<="
    def __call__(self, assigns, toplevel=0):
        from types import IntType
        tt = type
        lv = self.left.value(assigns)
        rv = self.right.value(assigns)
        result = assigns[:]
        for i in xrange(len(assigns)):
            t = assigns[i]
            if tt(t) is not IntType and lv[i]>rv[i]:
                result[i] = 0
        return result

    def __inv__(self):
        return LessPred(self.right, self.left)

class SubQueryExpression(BoundMinus, SimpleRecursive):
    """sub query expression (subq), must eval to single column, single value"""
    def __init__(self, subq):
        self.subq = subq
        self.att = self.cachable = self.cached = self.cached_value = None

    def initargs(self):
        return (self.subq,)

    def uncache(self):
        self.cached = self.cached_value = None

    def domain(self):
        result = self.subq.unbound()
        if not result:
            self.cachable = 1
        #print "expr subq domain", result
        return result

    def relbind(self, dict, db):
        subq = self.subq = self.subq.relbind(db, dict)
        # test that subquery is single column and determine att
        sl = subq.select_list
        atts = sl.attorder
        if len(atts)<>1:
            raise ValueError, \
              "Quantified predicate requires unit select list: %s" % atts
        self.att = atts[0]
        return self

    def __repr__(self):
        return "(%s)" % self.subq

    def value(self, contexts):
        subq = self.subq
        att = self.att
        if self.cachable:
            if self.cached:
                cached_value = self.cached_value
            else:
                self.cached = 1
                seval = subq.eval().rows()
                lse = len(seval)
                if lse<>1:
                    raise ValueError, \
           "const subquery expression must return 1 result: got %s" % lse
                self.cached_value = cached_value = seval[0][att]
            #print "const subq cached", cached_value
            return [cached_value] * len(contexts)
        from types import IntType
        tt = type
        result = contexts[:]
        kjDict = kjbuckets.kjDict
        for i in xrange(len(contexts)):
            contextsi = contexts[i]
            if tt(contextsi) is not IntType:
                testbtup = BoundTuple()
                testbtup.assns = kjDict(contextsi)
                #print "subq exp", testbtup
                seval = subq.eval(outerboundtuple=testbtup).rows()
                lse = len(seval)
                if lse<>1:
                    raise ValueError, \
           "dynamic subquery expression must return 1 result: got %s" % lse
                result[i] = seval[0][att]
                #print "nonconst subq uncached", result[i], contextsi
        return result

SELECT_TEMPLATE = """\
SELECT %s %s
FROM %s
WHERE %s
GROUP BY %s
HAVING %s %s
ORDER BY %s %s
"""

def dynamic_binding(ndynamic, dynamic):
    """create bindings from dynamic tuple for ndynamic parameters
       if a tuple is given create one
       if a list is given create many
    """
    from types import ListType, TupleType
    if not dynamic:
        if ndynamic>0:
            raise ValueError, `ndynamic`+" dynamic parameters unbound"
        return [kjbuckets.kjDict()]
    ldyn = len(dynamic)
    undumper = map(None, [0]*ndynamic, range(ndynamic))
    undumper = tuple(undumper)
    tdyn = type(dynamic)
    if tdyn is TupleType:
        ldyn = len(dynamic)
        if len(dynamic)!=ndynamic:
            raise ValueError, "%s,%s: wrong number of dynamics" % (ldyn,ndynamic)
        dynamic = [dynamic]
    elif tdyn is not ListType:
        raise TypeError, "dynamic parameters must be list or tuple"
    else:
        lens = map(len, dynamic)
        ndynamic = max(lens)
        if ndynamic!=min(lens):
            raise ValueError, "dynamic parameters of inconsistent lengths"
    undumper = map(None, [0]*ndynamic, range(ndynamic))
    undumper = tuple(undumper)
    result = list(dynamic)
    kjUndump = kjbuckets.kjUndump
    for i in xrange(len(dynamic)):
        dyn = dynamic[i]
        ldyn = len(dyn)
        #print undumper, dyn
        if ldyn==1:
            dynresult = kjUndump(undumper, dyn[0])
        else:
            dynresult = kjUndump(undumper, dyn)
        result[i] = dynresult
    return result

class Selector:
    """For implementing, eg the SQL SELECT statement."""
    def __init__(self, alldistinct,
                       select_list,
                       table_reference_list,
                       where_pred,
                       group_list,
                       having_cond,
                       union_select =None,
                       order_by_spec =None,
                       ndynamic=0, # number of dyn params expected
                       ):
        self.ndynamic = ndynamic
        self.alldistinct = alldistinct
        self.select_list = select_list
        self.table_list = table_reference_list
        self.where_pred = where_pred
        self.group_list = group_list
        self.having_cond = having_cond
        self.union_select = union_select
        self.order_by = order_by_spec
        #self.union_spec = "DISTINCT" # default union mode
        self.relbindings = None # binding of relations
        self.unbound_set = None # unbound attributes
        self.rel_atts = None # graph of alias>attname bound in self
        self.all_aggregate = 0
        if select_list!="*" and not group_list:
            if select_list.contains_aggregate:
            ### should restore this check somewhere else!
            #if select_list.contains_nonaggregate:
                  #raise ValueError, "aggregates/nonaggregates don't mix without grouping"
                self.all_aggregate = 1
        if where_pred and where_pred.contains_aggregate:
            raise ValueError, "aggregate in WHERE"
        self.query_plan = None

    def initargs(self):
        #print self.alldistinct
        #print self.select_list
        #print self.table_list
        #print self.where_pred
        #print self.having_cond
        #print self.union_select
        #print self.group_list
        #print self.order_by
        #print self.ndynamic
        # note: order by requires special handling
        return (self.alldistinct, self.select_list, self.table_list, self.where_pred,
                None, self.having_cond, self.union_select, None,
                self.ndynamic)

    def marshaldata(self):
        order_by = self.order_by
        if order_by:
            order_by = map(serialize, order_by)
        group_list = self.group_list
        if group_list:
            group_list = map(serialize, group_list)
        #print "marshaldata"
        #print order_by
        #print group_list
        return (order_by, group_list)

    def demarshal(self, data):
        (order_by, group_list) = data
        if order_by:
            order_by = map(deserialize, order_by)
        if group_list:
            group_list = map(deserialize, group_list)
        #print "demarshal"
        #print order_by
        #print group_list
        self.order_by = order_by
        self.group_list = group_list

    def unbound(self):
        result = self.unbound_set
        if result is None:
            raise ValueError, "binding not available"
        return result

    def uncache(self):
        wp = self.where_pred
        hc = self.having_cond
        sl = self.select_list
        if wp is not None: wp.uncache()
        if hc is not None: hc.uncache()
        sl.uncache()
        qp = self.query_plan
        if qp:
            for joiner in qp:
                joiner.uncache()

    def relbind(self, db, outerbindings=None):
        ad = self.alldistinct
        sl = self.select_list
        tl = self.table_list
        wp = self.where_pred
        gl = self.group_list
        hc = self.having_cond
        us = self.union_select
        ob = self.order_by
        test = db.bindings(tl)
        #print len(test)
        #for x in test:
            #print x
        (attbindings, relbindings, ambiguous, ambiguousatts) = test
        if outerbindings:
            # bind in outerbindings where unambiguous
            for (a,r) in outerbindings.items():
                if ((not attbindings.has_key(a))
                    and (not ambiguousatts.has_key(a)) ):
                    attbindings[a] = r
        # fix "*" select list
        if sl=="*":
            sl = TupleCollector()
            for (a,r) in attbindings.items():
                sl.addbinding(None, BoundAttribute(r,a))
            for (dotted, (r,a)) in ambiguous.items():
                sl.addbinding(dotted, BoundAttribute(r,a))
        sl = sl.relbind(attbindings, db)
        wp = wp.relbind(attbindings, db)
        if hc is not None: hc = hc.relbind(attbindings, db)
        if us is not None: us = us.relbind(db, attbindings)
        # bind grouping if present
        if gl:
            gl = relbind_sequence(gl, attbindings, db)
        # bind ordering list if present
        #print ob
        if ob:
            ob = relbind_sequence(ob, attbindings, db)
            ob = orderbind_sequence(ob, sl.order)
        result = Selector(ad, sl, tl, wp, gl, hc, us, ob)
        result.relbindings = relbindings
        result.ndynamic = self.ndynamic
        result.check_domains()
        result.plan_query()
        query_plan = result.query_plan
        for i in range(len(query_plan)):
            query_plan[i] = query_plan[i].relbind(db, attbindings)
        return result

    def plan_query(self):
        """generate a query plan (sequence of join operators)."""
        rel_atts = self.rel_atts # rel>attname
        where_pred = self.where_pred.detrivialize()
        #select_list = self.select_list
        # shortcut
        if where_pred is None:
            bt = BoundTuple()
        else:
            bt = self.where_pred.constraints
        if bt is None:
            bt = BoundTuple()
        eqs = kjbuckets.kjGraph(bt.eqs)
        witness = kjbuckets.kjDict()
        # set all known and unbound atts as witnessed
        for att in bt.assns.keys():
            witness[att] = 1
        #print self, "self.unbound_set", self.unbound_set
        for att in self.unbound_set.items():
            witness[att] = 1
        relbindings = self.relbindings
        allrels = relbindings.keys()
        #print relbindings
        allrels = bt.relorder(relbindings, allrels)
        #print allrels
        rel_atts = self.rel_atts
        plan = []
        for rel in allrels:
            relation = relbindings[rel]
            ratts = rel_atts.neighbors(rel)
            h = HashJoiner(bt, rel, ratts, relation, witness)
            plan.append(h)
            for a in ratts:
                ra = (rel, a)
                witness[ra] = 1
                eqs[ra] = ra
            witness = witness.remap(eqs)
        self.query_plan = plan

    def check_domains(self):
        """determine set of unbound names in self.
        """
        relbindings = self.relbindings
        sl = self.select_list
        wp = self.where_pred
        gl = self.group_list
        hc = self.having_cond
        us = self.union_select
        all = sl.domain().items()
        if wp is not None:
            all = all + wp.domain().items()
        # ignore group_list ???
        if hc is not None:
            all = all + hc.domain().items()
        kjSet = kjbuckets.kjSet
        kjGraph = kjbuckets.kjGraph
        alldomain = kjSet(all)
        rel_atts = self.rel_atts = kjGraph(all)
        allnames = kjSet()
        #print "relbindings", relbindings.keys()
        for name in relbindings.keys():
            rel = relbindings[name]
            for att in rel.attributes():
                allnames[ (name, att) ] = 1
        # union compatibility check
        if us is not None:
            us.check_domains()
            myatts = self.attributes()
            thoseatts = us.attributes()
            if myatts!=thoseatts:
                if len(myatts)!=len(thoseatts):
                    raise IndexError, "outer %s, inner %s: union select lists lengths differ"\
                      % (len(myatts), len(thoseatts))
            for p in map(None, myatts, thoseatts):
                (x,y)=p
                if x!=y:
                    raise NameError, "%s union names don't match" % (p,)
        self.unbound_set = alldomain - allnames

    def attributes(self):
        return self.select_list.attorder

    def eval(self, dynamic=None, outerboundtuple=None):
        """leaves a lot to be desired.
           dynamic and outerboundtuple are mutually
           exclusive.  dynamic is only pertinent to
           top levels, outerboundtuple to subqueries"""
        #print "select eval", dynamic, outerboundtuple
        from gfdb0 import Relation0
        # only uncache if outerboundtuple is None (not subquery)
        # ???
        if outerboundtuple is None:
            self.uncache()
        query_plan = self.query_plan
        where_pred = self.where_pred.detrivialize()
        select_list = self.select_list
        # shortcut
        if where_pred is not None and where_pred.false:
            return Relation0(select_list.attorder, [])
        #print "where_pred", where_pred
        if where_pred is None or where_pred.constraints is None:
            assn0 = assn1 = kjbuckets.kjDict()
        else:
            assn1 = self.where_pred.constraints.assns
            assn0 = assn1 = kjbuckets.kjDict(assn1)
        # erase stored results from possible previous evaluation
        ndynamic = self.ndynamic
        if outerboundtuple is not None:
            assn1 = assn1 + outerboundtuple.assns
        elif ndynamic:
            dyn = dynamic_binding(ndynamic, dynamic)
            if len(dyn)!=1:
                raise ValueError, "only one dynamic subst for selection allowed"
            dyn = dyn[0]
            assn1 = assn1 + dyn
            #print "dynamic", bt
        #print "assn1", assn1
        # check unbound names
        unbound_set = self.unbound_set
        #print "unbound", unbound_set
        #print unbound_set
        #print self.rel_atts
        for pair in unbound_set.items():
            if not assn1.has_key(pair):
                raise KeyError, `pair`+": unbound in selection"
        assn1 = (unbound_set * assn1) + assn0
        #print "assn1 now", assn1
        substseq = [assn1]
        for h in query_plan:
            #print "***"
            #for x in substseq:
                #print x
            #print "***"
            substseq = h.join(substseq)
            if not substseq: break
            #print "***"
            #for x in substseq:
                #print x
            #print "***"
        # apply the rest of the where predicate at top level
        if substseq and where_pred is not None:
            #where_pred.uncache()
            substseq = where_pred(substseq, 1)
        # eliminate zeros/nulls
        substseq = no_ints_nulls(substseq)
        # apply grouping if present
        group_list = self.group_list
        if substseq and group_list:
            substseq = aggregate(substseq, group_list)
            having_cond = self.having_cond
            #print having_cond
            if having_cond is not None:
                #having_cond.uncache()
                substseq = no_ints_nulls(having_cond(substseq))
        elif self.all_aggregate:
            # universal group
            substseq = [kjbuckets.kjDict( [(None, substseq)] ) ]
        (tups, attorder) = select_list.map(substseq)
        # do UNION if present
        union_select = self.union_select
        if union_select is not None:
            tups = union_select.eval(tups, dynamic, outerboundtuple)
        # apply DISTINCT if appropriate
        if self.alldistinct=="DISTINCT":
            tups = kjbuckets.kjSet(tups).items()
        # apply ordering if present
        ob = self.order_by
        if ob:
            tups = order_tuples(ob, tups)
        return Relation0(attorder, tups)

    def __repr__(self):
        ndyn = ""
        if self.ndynamic:
            ndyn = "\n[%s dynamic parameters]" % self.ndynamic
        result = SELECT_TEMPLATE % (
          self.alldistinct,
          self.select_list,
          self.table_list,
          self.where_pred,
          self.group_list,
          self.having_cond,
          #union_spec,
          self.union_select,
          self.order_by,
          ndyn
          )
        return result

class Union(SimpleRecursive):
    """union clause."""

    def __init__(self, alldistinct, selection):
        self.alldistinct = alldistinct
        self.selection = selection

    def initargs(self):
        return (self.alldistinct, self.selection)

    def unbound(self):
        return self.selection.unbound()

    def relbind(self, db, outer=None):
        self.selection = self.selection.relbind(db, outer)
        return self

    def check_domains(self):
        self.selection.check_domains()

    def attributes(self):
        return self.selection.attributes()

    def eval(self, assns, dyn=None, outer=None):
        r = self.selection.eval(dyn, outer)
        rows = r.rows()
        allrows = rows + assns
        if self.alldistinct=="DISTINCT":
            allrows = kjbuckets.kjSet(allrows).items()
        return allrows

    def __repr__(self):
        return "\nUNION %s %s " % (self.alldistinct, self.selection)

class Intersect(Union):
    def eval(self, assns, dyn=None, outer=None):
        r = self.selection.eval(dyn, outer)
        rows = r.rows()
        kjSet = kjbuckets.kjSet
        allrows = (kjSet(assns) & kjSet(rows)).items()
        return allrows
    op = "INTERSECT"
    def __repr__(self):
        return "\n%s %s" % (self.op, self.selection)

class Except(Union):
    def eval(self, assns, dyn=None, outer=None):
        r = self.selection.eval(dyn, outer)
        rows = r.rows()
        kjSet = kjbuckets.kjSet
        allrows = (kjSet(assns) - kjSet(rows)).items()
        return allrows
    op = "EXCEPT"


class Parse_Context:
    """contextual information for parsing
         p.param() returns a new sequence number for external parameter.
    """
    # not serializable

    parameter_index = 0

    # no __init__ yet
    def param(self):
        temp = self.parameter_index
        self.parameter_index = temp+1
        return temp

    def ndynamic(self):
        return self.parameter_index
# update/delete/insert statements
import sqlmod
CreateTable = sqlmod.CreateTable
CreateIndex = sqlmod.CreateIndex
DropIndex = sqlmod.DropIndex
DropTable = sqlmod.DropTable
UpdateOp = sqlmod.UpdateOp
DeleteOp = sqlmod.DeleteOp
InsertOp = sqlmod.InsertOp
InsertValues = sqlmod.InsertValues
InsertSubSelect = sqlmod.InsertSubSelect
ColumnDef = sqlmod.ColumnDef
CreateView = sqlmod.CreateView
DropView = sqlmod.DropView

# update storage structures from gfdb0
import gfdb0
Add_Tuples = gfdb0.Add_Tuples
Erase_Tuples = gfdb0.Erase_Tuples
Reset_Tuples = gfdb0.Reset_Tuples

####### testing
# test helpers
#def tp(**kw):
#    return maketuple(kw)

#def st(**kw):
#    return BTPredicate(BoundTuple(r=kw))
