"""Database modification statement semantics""" import sqlsem # ordering of ddef storage is important so, eg, index defs # follow table defs. class Ordered_DDF: """mixin for DDF statement sorting, subclass defines s.cmp(o)""" def __cmp__(self, other): try: #print "comparing", self.name, other.name try: sc = self.__class__ oc = other.__class__ #print sc, oc except: #print "punting 1", -1 return -1 if sc in ddf_order and oc in ddf_order: test = cmp(ddf_order.index(sc), ddf_order.index(oc)) #print "ddforder", test if test: return test return self.cmp(other) else: test = cmp(sc, oc) #print "punting 2", test return test except: #import sys #print "exception!" #print sys.exc_type, sys.exc_value return -1 def __coerce__(self, other): return (self, other) def cmp(self, other): """redefine if no name field""" return cmp(self.name, other.name) CTFMT = """\ CREATE TABLE %s ( %s )""" class CreateTable(Ordered_DDF): """create table operation""" def __init__(self, name, colelts): self.name = name self.colelts = colelts self.indb = None # db in which to create def initargs(self): return (self.name, []) def marshaldata(self): from sqlsem import serialize return map(serialize, self.colelts) def demarshal(self, args): from sqlsem import deserialize self.colelts = map(deserialize, args) def __repr__(self): from string import join elts = list(self.colelts) elts = map(repr, elts) return CTFMT % (self.name, join(elts, ",\n ")) def relbind(self, db): """check that table doesn't already exist""" if db.has_relation(self.name): raise NameError, "cannot create %s, exists" % (self.name,) self.indb = db return self def eval(self, dyn=None): "create the relation now" # datatypes currently happily ignored :) db = self.indb if db is None: raise ValueError, "unbound or executed" self.indb = None name = self.name if db.has_relation(self.name): raise NameError, "relation %s exists, cannot create" % (self.name,) db.touched = 1 attnames = [] for x in self.colelts: attnames.append(x.colid) from gfdb0 import Relation0 r = Relation0(attnames) # must store if new (unset for reloads) r.touched = 1 db[name] = r db.add_datadef(name, self) log = db.log if log is not None: log.log(self) viewfmt = """\ CREATE VIEW %s (%s) AS %s""" class CreateView(sqlsem.SimpleRecursive, Ordered_DDF): """CREATE VIEW name (namelist) AS selection""" # note: no check for cross-references on drops! def __init__(self, name, namelist, selection): self.name = name self.namelist = namelist self.selection = selection self.indb = None def __repr__(self): return viewfmt % (self.name, self.namelist, self.selection) def initargs(self): return (self.name, self.namelist, self.selection) def relbind(self, db): self.indb = db name = self.name if db.has_datadef(name): raise NameError, "(view) datadef %s exists" % name # don't bind the selection yet return self def eval(self, dyn=None): "create the view" db = self.indb name = self.name if db is None: raise ValueError, "create view %s unbound or executed" % name self.indb = None if db.has_relation(name): raise ValueError, "create view %s, name exists" % name db.touched = 1 from gfdb0 import View v = View(self.name, self.namelist, self.selection, db) db[name] = v db.add_datadef(name, self) log = db.log if log is not None: log.log(self) CREATEINDEXFMT = """\ CREATE %sINDEX %s ON %s ( %s )""" class CreateIndex(sqlsem.SimpleRecursive, Ordered_DDF): """create index operation""" def __init__(self, name, tablename, atts, unique=0): self.name = name self.tablename=tablename self.atts = atts self.indb = None self.target = None self.unique = unique def initargs(self): return (self.name, self.tablename, self.atts, self.unique) def __cmp__(self, other): oc = other.__class__ if oc is CreateTable: return 1 # after all create tables sc = self.__class__ if oc is not sc: return cmp(sc, oc) else: return cmp(self.name, other.name) def __coerce__(self, other): return (self, other) def __repr__(self): from string import join un = "" if self.unique: un="UNIQUE " innards = join(self.atts, ",\n ") return CREATEINDEXFMT % (un, self.name, self.tablename, innards) def relbind(self, db): name = self.name self.indb = db if db.has_datadef(name): raise NameError, `name`+": data def exists" try: self.target = db.get_for_update(self.tablename) #db[self.tablename] except: raise NameError, `self.tablename`+": no such relation" return self def eval(self, dyn=None): from gfdb0 import Index db = self.indb if db is None: raise ValueError, "create index unbound or executed" self.indb = None rel = self.target if rel is None: raise ValueError, "create index not bound to relation" db.touched = 1 self.the_index = the_index = Index(self.name, self.atts, unique=self.unique) rel.add_index(the_index) name = self.name db.add_datadef(name, self) db.add_index(name, the_index) log = db.log if log is not None: log.log(self) class DropIndex(sqlsem.SimpleRecursive): def __init__(self, name): self.name = name self.indb = None def initargs(self): return (self.name,) def __repr__(self): return "DROP INDEX %s" % (self.name,) def relbind(self, db): self.indb = db if not db.has_datadef(self.name): raise NameError, `self.name`+": no such index" return self def eval(self, dyn=None): db = self.indb self.indb=None if db is None: raise ValueError, "drop index executed or unbound" db.touched = 1 indexname = self.name createindex = db.datadefs[indexname] index = createindex.the_index relname = createindex.tablename rel = db[relname] rel.drop_index(index) db.drop_datadef(indexname) db.drop_index(indexname) log = db.log if log is not None: log.log(self) class DropTable(sqlsem.SimpleRecursive): def __init__(self, name): self.name = name self.indb = None def initargs(self): return (self.name,) def __repr__(self): return "DROP TABLE %s" % (self.name,) def relbind(self, db): self.indb = db name = self.name if not db.has_relation(name): raise NameError, `self.name` + ": cannot delete, no such table/view" self.check_kind(name, db) return self def check_kind(self, name, db): if db[name].is_view: raise ValueError, "%s is VIEW, can't DROP TABLE" % name def eval(self, dyn): db = self.indb if db is None: raise ValueError, "unbound or executed" db.touched = 1 self.indb = None self.relbind(db) name = self.name rel = db[name] rel.drop_indices(db) db.drop_datadef(name) del db[name] log = db.log if log is not None: log.log(self) class DropView(DropTable): """DROP VIEW name""" def __repr__(self): return "DROP VIEW %s" % self.name def check_kind(self, name, db): if not db[name].is_view: raise ValueError, "%s is TABLE, can't DROP VIEW" % name COLDEFFMT = "%s %s %s %s" class ColumnDef(sqlsem.SimpleRecursive): def __init__(self, colid, datatype, defaults, constraints): self.colid = colid self.datatype = datatype self.defaults = defaults self.constraints = constraints def initargs(self): return (self.colid, self.datatype, self.defaults, self.constraints) def __repr__(self): defaults = self.defaults if defaults is None: defaults="" constraints = self.constraints if constraints is None: constraints = "" return COLDEFFMT % (self.colid, self.datatype, defaults, constraints) def evalcond(cond, eqs, target, dyn, rassns, translate, invtrans): """factored out shared op between Update and Delete.""" if dyn: #print "dyn", dyn from sqlsem import dynamic_binding dynbind = dynamic_binding(len(dyn), dyn) if len(dynbind)>1: raise ValueError, "only one dynamic binding allowed for UPDATE" dynbind1 = dynbind = dynbind[0] if eqs is not None: dynbind1 = dynbind.remap(eqs) if dynbind1 is None: # inconsistent return dynbind = dynbind1 + dynbind if rassns is not None: rassns = rassns + invtrans * dynbind if rassns.Clean() is None: # inconsistent return else: rassns = invtrans * dynbind #print "dynbind", dynbind #print "rassn", rassns else: dynbind = None # get tuple set, try to use an index index = None if rassns is not None: known = rassns.keys() index = target.choose_index(known) if index is None: (tuples, seqnums) = target.rows(1) else: #print "using index", index.name (tuples, seqnums) = index.matches(rassns) ltuples = len(tuples) buffer = [0] * ltuples rtups = range(ltuples) for i in rtups: tup = tuples[i] #print tup ttup = translate * tup if dynbind: ttup = (ttup + dynbind).Clean() if ttup is not None: buffer[i] = ttup #print "buffer", buffer #print "cond", cond #for x in buffer: #print "before", x test = cond(buffer) #print "test", test return (test, rtups, seqnums, tuples) UPDFMT = """\ UPDATE %s SET %s WHERE %s""" # optimize to use indices and single call to "cond" class UpdateOp(sqlsem.SimpleRecursive): def __init__(self, name, assns, condition): self.name = name self.assns = assns self.condition = condition def initargs(self): return (self.name, self.assns, self.condition) def __repr__(self): return UPDFMT % (self.name, self.assns, self.condition) def relbind(self, db): self.indb = db name = self.name target = self.target = db.get_for_update(name) (attb, relb, amb, ambatts) = db.bindings( [ (name, name) ] ) assns = self.assns = self.assns.relbind(attb, db) cond = self.condition = self.condition.relbind(attb, db) constraints = cond.constraints if constraints is not None: eqs = self.eqs = constraints.eqs cassns = constraints.assns else: cassns = eqs = self.eqs = None #print constraints, eqs # check that atts of assns are atts of target #print dir(assns) resultatts = assns.attorder from sqlsem import kjbuckets kjSet = kjbuckets.kjSet kjGraph = kjbuckets.kjGraph resultatts = kjSet(resultatts) allatts = kjSet(target.attribute_names) self.preserved = allatts - resultatts huh = resultatts - allatts if huh: raise NameError, "%s lacks %s attributes" % (name, huh.items()) # compute projection assnsatts = kjGraph(assns.domain().items()).neighbors(name) condatts = kjGraph(cond.domain().items()).neighbors(name) condatts = condatts+assnsatts #print "condatts", condatts translate = kjbuckets.kjDict() for att in condatts: translate[ (name, att) ] = att self.translate = translate invtrans= self.invtrans = ~translate if cassns is not None: self.rassns = invtrans * cassns else: self.rassns = None #print "cassns,rassns", cassns, self.rassns #print translate # compute domain of self.assns # (do nothing with it, should add sanity check!) assns_domain = self.assns.domain() return self def eval(self, dyn=None): indb = self.indb name = self.name cond = self.condition cond.uncache() assns = self.assns assns.uncache() translate = self.translate preserved = self.preserved target = self.target rassns = self.rassns eqs = self.eqs invtrans = self.invtrans #print "assns", assns, assns.__class__ #print "cond", cond #print "eqs", eqs #print "target", target #print "dyn", dyn #print "rassns", rassns #print "translate", translate #print "invtrans", invtrans (test, rtups, seqnums, tuples) = evalcond( cond, eqs, target, dyn, rassns, translate, invtrans) # shortcut if not test: return self.indb.touched = 1 tt = type from types import IntType #print test (tps, attorder) = assns.map(test) count = 0 newseqs = list(rtups) newtups = list(rtups) for i in rtups: new = tps[i] if tt(new) is not IntType and new is not None: seqnum = seqnums[i] old = tuples[i] if preserved: new = new + preserved*old newtups[count] = new newseqs[count] = seqnum count = count + 1 if count: newseqs = newseqs[:count] newtups = newtups[:count] target.reset_tuples(newtups, newseqs) log = indb.log if log is not None and not log.is_scratch: from sqlsem import Reset_Tuples op = Reset_Tuples(self.name) op.set_data(newtups, newseqs, target) log.log(op) class DeleteOp(sqlsem.SimpleRecursive): def __init__(self, name, where): self.name = name self.condition = where def initargs(self): return (self.name, self.condition) def __repr__(self): return "DELETE FROM %s WHERE %s" % (self.name, self.condition) def relbind(self, db): self.indb = db name = self.name target = self.target = db.get_for_update(name) (attb, relb, amb, ambatts) = db.bindings( [ (name, name) ] ) cond = self.condition = self.condition.relbind(attb, db) # compute domain of cond # do nothing with it (should add sanity check) cond_domain = cond.domain() constraints = cond.constraints if constraints is not None: cassns = constraints.assns self.eqs = constraints.eqs else: self.eqs = cassns = None # compute projection/rename from sqlsem import kjbuckets condatts = kjbuckets.kjGraph(cond.domain().items()).neighbors(name) translate = kjbuckets.kjDict() for att in condatts: translate[(name, att)] = att self.translate = translate invtrans = self.invtrans = ~translate if cassns is not None: self.rassns = invtrans * cassns else: self.rassns = None return self def eval(self, dyn=None): # note, very similar to update case... indb = self.indb name = self.name target = self.target tuples = target.tuples eqs = self.eqs rassns = self.rassns cond = self.condition cond.uncache() translate = self.translate invtrans = self.invtrans (test, rtups, seqnums, tuples) = evalcond( cond, eqs, target, dyn, rassns, translate, invtrans) # shortcut if not test: return indb.touched = 1 tt = type from types import IntType count = 0 newseqs = list(rtups) #print "rtups", rtups for i in rtups: new = test[i] if tt(new) is not IntType and new is not None: seqnum = seqnums[i] newseqs[count] = seqnum count = count + 1 #print "newseqs", newseqs #print "count", count if count: newseqs = newseqs[:count] target.erase_tuples(newseqs) log = indb.log if log is not None and not log.is_scratch: from sqlsem import Erase_Tuples op = Erase_Tuples(self.name) op.set_data(newseqs, target) log.log(op) INSFMT = """\ INSERT INTO %s %s %s""" class InsertOp(sqlsem.SimpleRecursive): def __init__(self, name, optcolids, insertspec): self.name = name self.optcolids = optcolids self.insertspec = insertspec self.target = None # target relation self.collector = None # name map for attribute translation def initargs(self): return (self.name, self.optcolids, self.insertspec) def __repr__(self): return INSFMT % (self.name, self.optcolids, self.insertspec) def relbind(self, db): self.indb = db name = self.name # determine target relation target = self.target = db.get_for_update(name) targetatts = target.attributes() from sqlsem import kjbuckets kjSet = kjbuckets.kjSet targetset = kjSet(targetatts) # check or set colid bindings colids = self.optcolids if colids is None: colids = self.optcolids = target.attributes() colset = kjSet(colids) ### for now all attributes must be in colset cdiff = colset-targetset if cdiff: raise NameError, "%s: no such attributes in %s" % (cdiff.items(), name) cdiff = targetset-colset ### temporary!!! if cdiff: raise NameError, "%s: not set in insert on %s" % (cdiff.items(), name) # bind the insertspec insertspec = self.insertspec self.insertspec = insertspec = insertspec.relbind(db) # create a collector for result from sqlsem import TupleCollector collector = self.collector = TupleCollector() # get ordered list of expressions to eval on bound attributes of insertspec resultexps = insertspec.resultexps() if len(resultexps)!=len(colset): raise ValueError, "result and colset of differing length %s:%s" % (colset,resultexps) pairs = map(None, colids, resultexps) for (col,exp) in pairs: collector.addbinding(col, exp) return self def eval(self, dyn=None): resultbts = self.insertspec.eval(dyn) #print "resultbts", resultbts # shortcut if not resultbts: return indb = self.indb indb.touched = 1 (resulttups, resultatts) = self.collector.map(resultbts) #print "resulttups", resulttups if resulttups: target = self.target target.add_tuples(resulttups) #target.regenerate_indices() log = indb.log if log is not None and not log.is_scratch: from sqlsem import Add_Tuples op = Add_Tuples(self.name) op.set_data(resulttups, target) log.log(op) Insert_dummy_arg = [ ( (1,1), 1 ) ] class InsertValues(sqlsem.SimpleRecursive): def __init__(self, List): self.list = List def initargs(self): return (self.list,) def __repr__(self): return "VALUES " +` tuple(self.list) ` def resultexps(self): return self.list def relbind(self, db): l = self.list bindings = {} for i in xrange(len(self.list)): li = l[i] l[i] = li.relbind(bindings, db) # do nothing with domain, for now li_domain = li.domain() return self def eval(self, dyn=None): if dyn: from sqlsem import dynamic_binding dynbt = dynamic_binding(len(dyn), dyn) else: # dummy value to prevent triviality from sqlsem import kjbuckets dynbt = [kjbuckets.kjDict(Insert_dummy_arg)] #print "bindings", dynbt.assns return dynbt # ?? class InsertSubSelect(sqlsem.SimpleRecursive): def __init__(self, subsel): self.subsel = subsel def initargs(self): return (self.subsel,) def __repr__(self): return "[subsel] %s" % (self.subsel,) def resultexps(self): # get list of result bindings subsel = self.subsel atts = self.subsel.attributes() # bind each as "result.name" exps = [] from sqlsem import BoundAttribute for a in atts: exps.append( BoundAttribute("result", a) ) return exps # temp def relbind(self, db): subsel = self.subsel self.subsel = subsel.relbind(db) # do nothing with domain for now #subsel_domain = subsel.domain() return self def eval(self, dyn=None): subsel = self.subsel subsel.uncache() rel = subsel.eval(dyn) tups = rel.rows() from sqlsem import BoundTuple ### temp from sqlsem import kjbuckets kjDict = kjbuckets.kjDict for i in xrange(len(tups)): tupsi = tups[i] new = kjDict() for k in tupsi.keys(): new[ ("result", k) ] = tupsi[k] tups[i] = new return tups # ordering for archiving datadefs ddf_order = [CreateTable, CreateIndex, CreateView]