class Map(Type): """Map type.""" key = TypedField(Type) value = TypedField(Type) def __str__(self): return '{{{}: {}}}'.format(self.key, self.value) def smaller_cmp(self, other): if type(self) != type(other): return NotImplemented return (self.key.issmaller(other.key) and self.value.issmaller(other.value)) def join_helper(self, other, *, inverted=False): top = Top if not inverted else Bottom if type(self) != type(other): return top new_key = self.key.join(other.key, inverted=inverted) new_value = self.value.join(other.value, inverted=inverted) return self._replace(key=new_key, value=new_value) def widen_helper(self, height): new_key = self.key.widen(height - 1) new_value = self.value.widen(height - 1) return self._replace(key=new_key, value=new_value)
class DictType(Type): kt = TypedField(Type) vt = TypedField(Type) # Contravariant key types were also considered as a possibility. # This would affect each of the helpers below. def __str__(self): return '{' + str(self.kt) + ': ' + str(self.vt) + '}' def issubtype_helper(self, other): if type(self) != type(other): return False return (self.kt.issubtype(other.kt) and self.vt.issubtype(other.vt)) def join_helper(self, other, *, inverted=False): if type(self) != type(other): return toptype if not inverted else bottomtype new_kt = self.kt.join(other.kt, inverted=inverted) new_vt = self.vt.join(other.vt, inverted=inverted) return self._replace(kt=new_kt, vt=new_vt) def match_against_helper(self, other): return [(self.kt, self.kt), (other.vt, other.vt)] def expand(self, store): new_kt = self.kt.expand(store) new_vt = self.vt.expand(store) return self._replace(kt=new_kt, vt=new_vt) def widen_helper(self, limit): new_kt = self.kt.widen(limit - 1) new_vt = self.vt.widen(limit - 1) return self._replace(kt=new_kt, vt=new_vt)
class TClause_NoTC(TClause): """TClause without type checks in emitted code.""" tup = TypedField(str) elts = TypedField(str, seq=True) typecheck = False
class SingletonClause(Clause, ABCStruct): """An enumerator over a singleton set, i.e., that binds its left-hand side to a single value. """ kind = Clause.KIND_ENUM robust = False lhs = TypedField(str, seq=True) """Enumeration variables.""" val = TypedField(L.expr) """Expression computing value of singleton element.""" @classmethod def from_expr(cls, node): """Construct from a condition expression of form <vars> == <rel> """ checktype(node, L.AST) left, op, val = L.get_cmp(node) checktype(op, L.Eq) lhs = L.get_vartuple(left) return cls(lhs, val) @classmethod def from_AST(cls, node, factory): """Construct from Enumerator node of form <vars> in {<expr>} """ checktype(node, L.Enumerator) lhs = L.get_vartuple(node.target) val = L.get_singletonset(node.iter) return cls(lhs, val) def __init__(self, lhs, val): self.enumlhs = self.lhs self.enumrel = None self.vars = self.enumvars def to_AST(self): return L.Enumerator(L.tuplify(self.lhs, lval=True), L.Set( (self.val, ))) def rate(self, bindenv): return Rate.CONSTANT def get_code(self, bindenv, body): mask = Mask.from_vars(self.lhs, bindenv) bvars, uvars, _eqs = mask.split_vars(self.lhs) return make_tuplematch(self.val, mask, bvars, uvars, body)
class IndefImgset(Cost): """Indefinite image set, i.e., the size of the largest image set under any key for a given relation and mask. """ rel = TypedField(str) mask = TypedField(L.mask) def __str__(self): return '{}_{}'.format(self.rel, self.mask.m)
class Filter(Struct): _immutable = False i = TypedField(int) """Index of clause for which this filter is generated.""" name = TypedField(str) """Name of this filter.""" clause = TypedField(L.clause) """Clause that this filter is based on.""" preds = TypedField(str, seq=True) """Names of predecessor tags for this filter."""
class Tag(Struct): _immutable = False i = TypedField(int) """Index of clause for which this tag is generated.""" name = TypedField(str) """Name of this tag.""" tag_var = TypedField(str) """Query variable controlled by this tag.""" clause = TypedField(L.clause) """Clause that this tag projects."""
class WrapInvariant(Struct): """Wrap invariant.""" rel = TypedField(str) """Name of variable holding the relation to be maintained.""" oper = TypedField(str) """Name of variable holding the operand relation.""" unwrap = TypedField(bool) """True for Unwrap, False for Wrap.""" def get_maint_func_name(self, op): op_name = L.set_update_name(op) return N.get_maint_func_name(self.rel, self.oper, op_name)
class Task(Struct): """A single transformation task.""" display_name = TypedField(str) """Display name for status printing.""" input_name = TypedField(str) """Input filename.""" output_name = Field() """Output filename, or None if no transformation.""" nopts = Field() """Normal options.""" qopts = Field() """Query options."""
class LookupClause(EnumClause, ABCStruct): """An enumerator over a singleton set of an SMLookup node. Basically acts like a normal EnumClause, but the forward direction takes constant time due to the functional dependency from keys to value. """ lhs = TypedField(str, seq=True) """Enumeration variables.""" rel = TypedField(str) """Name of iterated relation.""" @classmethod def from_AST(cls, node, factory): """Construct from an Enumerator node of form var in {<rel>.smlookup(<mask>, <key vars>)} """ checktype(node, L.Enumerator) var = L.get_name(node.target) sm = L.get_singletonset(node.iter) checktype(sm, L.SMLookup) rel = L.get_name(sm.target) mask = Mask(sm.mask) keyvars = L.get_vartuple(sm.key) # Ensure the mask is consistent with how it's used. if mask != Mask.from_keylen(len(keyvars)): raise TypeError lhs = keyvars + (var, ) return cls(lhs, rel) def to_AST(self): mask = Mask.from_keylen(len(self.lhs) - 1) keyvars = self.lhs[:-1] var = self.lhs[-1] sm = L.SMLookup(L.ln(self.rel), mask.make_node().s, L.tuplify(keyvars), None) return L.Enumerator(L.sn(var), L.Set((sm, ))) def rewrite_subst(self, subst, factory): # The normal rewriting won't get the smlookup keys. new_lhs = apply_subst_tuple(self.lhs, subst) return self._replace(lhs=new_lhs) def rate(self, bindenv): mask = Mask.from_vars(self.lhs, bindenv) if mask.is_keymask: return Rate.CONSTANT return super().rate(bindenv)
class DefImgsetCost(Cost): rel = TypedField(str) mask = TypedField(Mask) key = TypedField(str, seq=True) def __str__(self): return '{}_{}[{}]'.format(self.rel, self.mask, ', '.join(self.key)) def to_indef(self): """Return the indefinite image set cost that generalizes this cost. """ return IndefImgsetCost(self.rel, self.mask)
class USet(Struct): kind = KIND_USET i = TypedField(int) """Index of query enumerator where the subquery is iterated over.""" name = TypedField(str) """Name of associated demand name.""" vars = TypedField(str, seq=True) """Vars that get passed to the demand functions as parameters.""" preds = Field() """Names of predecessor tags, or None if using clauses.""" pred_clauses = Field() """Predecessor clauses, or None if using tags.""" reorder_i = TypedField(int) """Relative order for demand graph."""
class Sum(Cost): """Sum of a sequence of costs.""" terms = TypedField(Cost, seq=True) def __str__(self): return '(' + ' + '.join(str(t) for t in self.terms) + ')'
class Sequence(Type): """Sequence of homogeneous elements. Covariant in element type.""" elt = TypedField(Type) def __str__(self): return 'Seq<' + str(self.elt) + '>' def smaller_cmp(self, other): # Two Sequence-based types are comparable if they are of the # same Sequence subtype or if one of the types is a Sequence # proper. In either case it then comes down to the element type. if (type(self) != type(other) and type(other) != Sequence): return NotImplemented return self.elt.issmaller(other.elt) def join_helper(self, other, *, inverted=False): # The join of two distinct Sequence-based types is a Sequence # proper. top = Top if not inverted else Bottom if (type(self) != type(other) and not issubclass(type(other), Sequence)): return top new_elt = self.elt.join(other.elt, inverted=inverted) if type(self) == type(other): return self._replace(elt=new_elt) else: return Sequence(new_elt) if not inverted else Bottom def widen_helper(self, height): new_elt = self.elt.widen(height - 1) return self._replace(elt=new_elt)
class SeqType(Type): et = TypedField(Type) brackets = '??' def __str__(self): return self.brackets[0] + str(self.et) + self.brackets[1] def issubtype_helper(self, other): if type(self) != type(other): return False return self.et.issubtype(other.et) def join_helper(self, other, inverted=False): if type(self) != type(other): return toptype if not inverted else bottomtype new_et = self.et.join(other.et, inverted=inverted) return self._replace(et=new_et) def match_against_helper(self, other): return [(self.et, other.et)] def expand(self, store): new_et = self.et.expand(store) return self._replace(et=new_et) def widen_helper(self, limit): new_et = self.et.widen(limit - 1) return self._replace(et=new_et)
class Min(Cost): """Minimum of a sequence of costs.""" terms = TypedField(Cost, seq=True) def __str__(self): return 'min(' + ', '.join(str(t) for t in self.terms) + ')'
class Name(Cost): """Atomic cost, e.g. a domain size or a relation size.""" name = TypedField(str) def __str__(self): return self.name
class Tuple(Type): """Tuple type. For tuples of the same arity, covariant in the component types. """ elts = TypedField(Type, seq=True) def __str__(self): return '(' + ', '.join(str(e) for e in self.elts) + ')' def smaller_cmp(self, other): if type(self) != type(other): return NotImplemented if len(self.elts) != len(other.elts): return NotImplemented return all(e1.issmaller(e2) for e1, e2 in zip(self.elts, other.elts)) def join_helper(self, other, *, inverted=False): top = Top if not inverted else Bottom if type(self) != type(other): return top if len(self.elts) != len(other.elts): return top new_elts = [e1.join(e2, inverted=inverted) for e1, e2 in zip(self.elts, other.elts)] return self._replace(elts=new_elts) def widen_helper(self, height): new_elts = [e.widen(height - 1) for e in self.elts] return self._replace(elts=new_elts)
class IncAggr(Struct): """Info for incrementalizing an aggregate query.""" aggr = TypedField(L.Aggregate) """Aggregate node.""" spec = TypedField(AggrSpec) """Aggregate query info.""" name = TypedField(str) """Result set name.""" demname = Field() """Aggregate demand name, or None if not using demand.""" uset_lru = Field() """None or an integer bound for LRU cache size.""" half_demand = TypedField(bool) """If using demand and this is True, use the "half-demand" strategy. """ @property def has_demand(self): return self.demname is not None @property def tracks_counts(self): # Counts are needed to know when to remove entries from the # aggregate result map. If we're using the normal demand # strategy, we only remove entries when keys become undemanded, # so counts aren't needed. return not (self.has_demand and not self.half_demand) def __init__(self, aggr, spec, name, demname, uset_lru, half_demand): self.params = params = tuple(spec.params) """Aggregate parameters (same as operand parameters). Also same as aggregate demand parameters. """ self.aggrmask = Mask.from_keylen(len(params)) """Aggregate result retrieval mask.""" self.oper_deltamask = spec.relmask.make_delta_mask() """Mask for doing delta test upon change to aggregate operand.""" assert not (spec.has_oper_demand and not self.has_demand), \ 'Can\'t have non-demand-driven aggregate over demand-driven ' \ 'operand' assert not (half_demand and not self.has_demand), \ 'Can\'t use half-demand strategy when not using demand at all'
class ObjType(Type): name = TypedField(str) def __str__(self): return self.name def matches_helper(self, other): return False
class SetFromMapInvariant(Struct): """SetFromMap invariant.""" # The map key must be a tuple. The mask must be a mapmask with a # number of bound components equal to the arity of the map key. rel = TypedField(str) """Name of variable holding the relation to be created.""" map = TypedField(str) """Name of map being indexed.""" mask = TypedField(L.mask) """Mask for relating the map to the set.""" def __init__(self, rel, map, mask): assert L.is_mapmask(mask) def get_maint_func_name(self, op): assert op in ['assign', 'delete'] return N.get_maint_func_name(self.rel, self.map, op)
class Primitive(Type): """Built-in type.""" # Note that t holds a Python class, not a Type. t = TypedField(type) def __str__(self): return self.t.__name__
class Refine(Type): name = TypedField(str) base = TypedField(Type) def __str__(self): return '{}:{}'.format(self.name, self.base) def smaller_cmp(self, other): return self.base.issmaller(other) def join_helper(self, other, *, inverted=False): # Since we don't have Union types, the only direct ancestor of # this type is the base. Therefore, the join of this type with # any other type T is the same as the join of the base with T. # # The only descendants of this type are Bottom and other Refine # types. The Refine types only have one parent, so they don't # have any ancestors that are incomparable with this type. # Consequently, the meet of this type with any incomparable # type is Bottom. if not inverted: return self.base.join(other, inverted=inverted) else: return Bottom def widen_helper(self, height): # Suppose this type is Refine<name, T>, and that widening is # needed. Let U be a widened version of T with T <= U. # # We can't return Refine<..., U> as our widening, because it is # not generally true that Refine<..., T> <= Refine<..., U>. # Instead, our widening is just T itself. widened_base = self.base.widen(height - 1) if widened_base == self.base: # Turns out widening is not even needed. return self else: # Something has to give. Return the base with widening, but # give it one more level of leeway since we're getting rid # of ourselves. return self.base.widen(height)
class AuxmapInvariant(Struct): """Auxiliary map invariant.""" map = TypedField(str) """Name of variable holding the map to be created.""" rel = TypedField(str) """Name of relation being indexed.""" mask = TypedField(L.mask) """Mask for the indexing.""" unwrap_key = TypedField(bool) """Whether the bound part is a single unwrapped component.""" unwrap_value = TypedField(bool) """Whether the unbound part is a single unwrapped component.""" def __init__(self, map, rel, mask, unwrap_key, unwrap_value): assert not (unwrap_key and mask.m.count('b') > 1) assert not (unwrap_value and mask.m.count('u') > 1) def get_maint_func_name(self, op): op_name = L.set_update_name(op) return N.get_maint_func_name(self.map, self.rel, op_name)
class DefImgset(Cost): """Definite image set, i.e., the size of a particular image set under a given sequence of key variables, for the given relation and mask. """ rel = TypedField(str) mask = TypedField(L.mask) key = TypedField(str, seq=True) def __init__(self, rel, mask, key): assert mask.m.count('b') == len(key) def __str__(self): return '{}_{}[{}]'.format(self.rel, self.mask.m, ', '.join(self.key)) def to_indef(self): """Return the indefinite image-set cost that generalizes this cost. """ return IndefImgset(self.rel, self.mask)
class SumCost(Cost): terms = TypedField(Cost, seq=True) @classmethod def from_sums(cls, costs): """Form as the concatenation of other SumCosts.""" assert all(isinstance(c, SumCost) for c in costs) return SumCost(tuple(chain.from_iterable(c.terms for c in costs))) def __str__(self): return '(' + ' + '.join(str(s) for s in self.terms) + ')'
class ProductCost(Cost): terms = TypedField(Cost, seq=True) @classmethod def from_products(cls, costs): """Form as the concatenation of other ProductCosts.""" assert all(isinstance(c, ProductCost) for c in costs) return ProductCost(tuple(chain.from_iterable(c.terms for c in costs))) def __str__(self): return '(' + '*'.join(str(t) for t in self.terms) + ')'
class RestrictiveType(Type): name = TypedField(str) base = TypedField(Type) def __str(self): return self.name def issubtype_helper(self, other): return self.base.issubtype(other) def matches_helper(self, other): return False def join_helper(self, other, *, inverted=False): # My join with any type that's not directly comparable # is the same as my base's join with that type, since # my base is my only direct ancestor. # # My meet with any type that's not directly comparable # is bottom, since the only possible non-bottom subtypes # are other refinements, which would have no ancestors # that are incomparable to me. if not inverted: return self.base.join(other, inverted=inverted) else: return bottomtype def expand(self, store): new_base = self.base.expand(store) return self._replace(base=new_base) def widen_helper(self, limit): # Rather than lose information in the base, it's probably # better to just replace ourselves with the base. widened_base = self.base.widen(limit - 1) if self.base != widened_base: new_type = self.base.widen(limit) else: new_type = self return new_type
class DeltaInfo(Struct): """Information about maintenance joins.""" rel = TypedField(str) """Delta relation.""" elem = TypedField(L.AST) """Delta element expression AST.""" lhs = TypedField(str, seq=True) """Delta clause LHS identifier list.""" op = TypedField(str) """'add' or 'remove'.""" @classmethod def from_options(cls, options): """Construct from comprehension options dict. If delta info isn't provided, return None instead of an instance. """ if options is None or '_deltarel' not in options: return None rel = options['_deltarel'] elem = options['_deltaelem'] elem = L.pe(elem) lhs = options['_deltalhs'] lhs = L.get_vartuple(L.pe(lhs)) op = options['_deltaop'] return cls(rel, elem, lhs, op) def __init__(self, rel, elem, lhs, op): assert op in ['add', 'remove'] def updateopts(self, options): """Return a modified options dict with the delta keys set.""" options = dict(options) options['_deltarel'] = self.rel options['_deltaelem'] = L.ts(self.elem) options['_deltalhs'] = L.ts(L.tuplify(self.lhs, lval=True)) options['_deltaop'] = self.op return options
class CondClause(Clause, ABCStruct): """A condition expression clause.""" kind = Clause.KIND_COND cond = TypedField(L.expr) """Condition expression.""" @classmethod def from_AST(cls, node, factory): """Construct from expression node.""" checktype(node, L.expr) return cls(node) def __init__(self, cond): self.vars = tuple(L.VarsFinder.run(cond, ignore_functions=True)) if L.is_vareqcmp(cond): self.eqvars = L.get_vareqcmp(cond) else: self.eqvars = None def to_AST(self): return self.cond def fits_string(self, bindenv, s): return self.cond == L.pe(s) def rate(self, bindenv): if set(self.vars).issubset(bindenv): return Rate.CONSTANT else: return Rate.UNRUNNABLE def get_code(self, bindenv, body): assert set(self.vars).issubset(bindenv) code = L.pc(''' if COND: BODY ''', subst={ 'COND': self.cond, '<c>BODY': body }) return code