def compare_expr(self, expr: Expression): assert expr.operator in COMPARISONS (left, right) = expr.args left_path = dot_path(left) right_path = dot_path(right) # Normalize partial to LHS. if right_path: expr = reflect_expr(expr) left, right = right, left left_path, right_path = right_path, left_path left_field = "__".join(left_path[1:]) if left_path[1:] else "pk" if left_path and right_path: raise UnsupportedError(f"Unsupported partial expression: {expr}") # compare partials # self.filter &= COMPARISONS[expr.operator]( # left_field, self.translate_path_to_field(right_path) # ) else: # partial cmp grounded assert left_path if isinstance(right, Model): right = right.pk self.filter &= COMPARISONS[expr.operator](left_field, right)
def translate_in(expression, session, model, get_model): assert expression.operator == "In" left = expression.args[0] right = expression.args[1] # IN means at least something must be contained in the property. # There are two possible types of in operations. In both, the right hand side # should be a dot op. # Partial In: LHS is an expression if isinstance(left, Expression): path = dot_path(right) assert path return translate_dot( path, session, model, functools.partial(emit_subexpression, left, get_model) ) else: # Contains: LHS is not an expression. # TODO (dhatch) Missing check, left type must match type of the target? path = dot_path(right) assert path path, field_name = path[:-1], path[-1] return translate_dot( path, session, model, functools.partial(emit_contains, field_name, left) )
def test_dot_path(): single = Expression("Dot", [Variable("_this"), "created_by"]) assert dot_path(single) == ("created_by", ) double = Expression("Dot", [single, "username"]) assert dot_path(double) == ("created_by", "username") triple = Expression("Dot", [double, "first"]) assert dot_path(triple) == ("created_by", "username", "first")
def translate_isa(expression: Expression, session: Session, model, get_model): assert expression.operator == "Isa" left, right = expression.args if dot_path(left) == (): assert left == Variable("_this") else: for field_name in dot_path(left): _, model, __ = get_relationship(model, field_name) assert not right.fields, "Unexpected fields in isa expression" constraint_type = get_model(right.tag) return sql.true() if issubclass(model, constraint_type) else sql.false()
def translate_compare(expression: Expression, session: Session, model, get_model): (left, right) = expression.args left_path = dot_path(left) if left_path: path, field_name = left_path[:-1], left_path[-1] return translate_dot( path, session, model, functools.partial(emit_compare, field_name, right, expression.operator), ) else: assert left == Variable("_this") if not isinstance(right, model): return sql.false() if expression.operator not in ("Eq", "Unify"): raise UnsupportedError( f"Unsupported comparison: {expression}. Models can only be compared" " with `=` or `==`" ) primary_keys = [pk.name for pk in inspect(model).primary_key] pk_filter = sql.true() for key in primary_keys: pk_filter &= getattr(model, key) == getattr(right, key) return pk_filter
def in_expr(self, expr: Expression): assert expr.operator == "In" (left, right) = expr.args right_path = dot_path(right) if left == "_this" and right_path: if right_path[1:]: # _this in _this.foo.bar # _this in _some_var.foo.bar path = self.translate_path_to_field(right_path) # path = "__".join(right_path[1:]) self.filter &= COMPARISONS["Unify"]("pk", path) else: # _this in _this # _this in _some_var raise UnsupportedError( f"Unsupported partial expression: {expr}") elif isinstance(left, Variable) and right_path: if right_path[1:]: # var in _this.foo.bar # var in other_var.foo.bar # get the base query for the RHS of the `in` root = self while root.parent: root = root.parent base_query = root.get_query_from_var(right_path[0]) or root # Left is a variable => apply constraints to the subquery. if left not in base_query.variables: subquery_path = right_path[1:] model = get_model_by_path(base_query.model, subquery_path) base_query.variables[left] = FilterBuilder( model, parent=base_query, name=left) else: # This means we have two paths for the same variable # the subquery will handle the intersection pass # Get the model for the subfield subquery = base_query.variables[left] # <var> in <partial> # => set up <var> as a new filtered query over the model # filtered to the entries of right_path path = base_query.translate_path_to_field(right_path) field = OuterRef(path.name) if isinstance( path, F) else OuterRef(path) subquery.filter &= COMPARISONS["Unify"]("pk", field) # Maybe redundant, but want to be sure base_query.variables[left] = subquery else: # var in _this # var in other_var raise UnsupportedError( f"Unsupported partial expression: {expr}") else: # <value> in <partial> self.filter &= COMPARISONS["Unify"]("__".join(right_path[1:]), left)
def isa_expr(expr: Expression, model: Model, **kwargs): assert expr.operator == "Isa" (left, right) = expr.args for attr in dot_path(left): model = getattr(model, attr).field.related_model constraint_type = apps.get_model(django_model_name(right.tag)) assert not right.fields, "Unexpected fields in matches expression" return TRUE_FILTER if issubclass(model, constraint_type) else FALSE_FILTER
def isa_expr(self, expr: Expression): assert expr.operator == "Isa" (left, right) = expr.args left_path = dot_path(left) assert left_path[0] == self.name root = self.get_query_from_var(left_path[0]) model = get_model_by_path(root.model, left_path[1:]) ty = apps.get_model(django_model_name(right.tag)) assert not right.fields, "Unexpected fields in matches expression" assert issubclass( model, ty), "Inapplicable rule should have been filtered out"
def translate_expr(self, expr: Expression): """Translate a Polar expression to a Django Q object.""" assert isinstance(expr, Expression), "expected a Polar expression" # Check if either side of the expression starts with a lookup on # a variable. In which case, enter the subquery for that variable # instead and proceed as usual. if len(expr.args) == 2: left, right = expr.args left_path = dot_path(left) right_path = dot_path(right) if left_path: query = self.get_query_from_var(left_path[0]) if query and query != self: query.translate_expr(expr) return self if right_path: query = self.get_query_from_var(right_path[0]) if query and query != self: query.translate_expr(expr) return self if expr.operator in COMPARISONS: self.compare_expr(expr) elif expr.operator == "And": self.and_expr(expr) elif expr.operator == "Isa": self.isa_expr(expr) elif expr.operator == "In": self.in_expr(expr) elif expr.operator == "Not": self.not_expr(expr) else: raise UnsupportedError(f"Unsupported partial expression: {expr}") return self
def translate_isa(expression: Expression, session: Session, model, get_model): assert expression.operator == "Isa" left, right = expression.args left_path = dot_path(left) assert left_path[0] == Variable("_this") left_path = left_path[1:] # Drop _this. if left_path: for field_name in left_path: _, model, __ = get_relationship(model, field_name) assert not right.fields, "Unexpected fields in isa expression" constraint_type = get_model(right.tag) model_type = inspect(model, raiseerr=True).class_ return sql.true() if issubclass(model_type, constraint_type) else sql.false()
def compare_expr(expr: Expression, model: Model, path=(), **kwargs): assert expr.operator in COMPARISONS (left, right) = expr.args left_path = dot_path(left) if left_path: return COMPARISONS[expr.operator]("__".join(path + left_path), right) else: assert left == Variable("_this") if not isinstance(right, model): return FALSE_FILTER if expr.operator not in ("Eq", "Unify"): raise UnsupportedError( f"Unsupported comparison: {expr}. Models can only be compared" " with `=` or `==`") return COMPARISONS[expr.operator]("__".join(path + ("pk", )), right.pk)
def in_expr(expr: Expression, model: Model, path=(), **kwargs): assert expr.operator == "In" (left, right) = expr.args right_path = dot_path(right) assert right_path, "RHS of in must be a dot lookup" right_path = path + right_path if isinstance(left, Expression): if left.operator == "And" and not left.args: # An unconstrained partial is in a list if the list is non-empty. count = Count("__".join(right_path)) filter = COMPARISONS["Gt"]("__".join(right_path + ("count", )), 0) subquery = Subquery( model.objects.annotate(count).filter(filter).values("pk")) return contained_in("pk", subquery) else: return translate_expr(left, model, path=right_path, **kwargs) else: return COMPARISONS["Unify"]("__".join(right_path), left)
def test_dot_path(): non_dot = Expression("And", []) assert dot_path(non_dot) == () this = Variable("_this") assert dot_path(this) == (this, ) var = Variable("x") assert dot_path(var) == (var, ) single_dot = Expression("Dot", [this, "created_by"]) assert dot_path(single_dot) == (this, "created_by") double_dot = Expression("Dot", [single_dot, "username"]) assert dot_path(double_dot) == (this, "created_by", "username") triple_dot = Expression("Dot", [double_dot, "first"]) assert dot_path(triple_dot) == (this, "created_by", "username", "first")