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 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 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 _runtime_error(subkind, message, details): runtime_errors = { "Serialization": SerializationError(message, details), "Unsupported": UnsupportedError(message, details), "TypeError": PolarTypeError(message, details), "StackOverflow": StackOverflowError(message, details), "FileLoading": FileLoadingError(message, details), } return runtime_errors.get(subkind, PolarRuntimeError(message, details))
def translate_expr(expression: Expression, session: Session, model, get_model): assert isinstance(expression, Expression) if expression.operator in COMPARISONS: return translate_compare(expression, session, model, get_model) elif expression.operator == "Isa": return translate_isa(expression, session, model, get_model) elif expression.operator == "In": return translate_in(expression, session, model, get_model) elif expression.operator == "And": return translate_and(expression, session, model, get_model) else: raise UnsupportedError(f"Unsupported {expression}")
def and_expr(expr, type_name): q = Q() assert expr.operator == "And" for expression in expr.args: assert isinstance(expression, Expression) if expression.operator in COMPARISONS: q = q & compare_expr(expression, type_name) elif expression.operator == "And": q = q & and_expr(expression, type_name) elif expression.operator == "Isa": try: assert expression.args[1].tag == type_name except (AssertionError, IndexError, AttributeError, TypeError): raise UnsupportedError( f"Unimplemented partial isa operation {expression}.") else: raise UnsupportedError( f"Unimplemented partial operator {expression.operator}") return q
def translate_expr(expr: Expression, model: Model, **kwargs): """Translate a Polar expression to a Django Q object.""" assert isinstance(expr, Expression), "expected a Polar expression" if expr.operator in COMPARISONS: return compare_expr(expr, model, **kwargs) elif expr.operator == "And": return and_expr(expr, model, **kwargs) elif expr.operator == "Isa": return isa_expr(expr, model, **kwargs) elif expr.operator == "In": return in_expr(expr, model, **kwargs) elif expr.operator == "Not": return not_expr(expr, model, **kwargs) else: raise UnsupportedError(f"Unsupported partial expression: {expr}")
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 preprocess_expression( expression: Expression, variables: TGroupedExpressions ) -> Optional[Expression]: """Collect expressions over variables into ``variables``. Return the expression with those removed. """ # Walk expression and collect variable expressions new_expr: Optional[Expression] = expression if expression.operator == "And": new_expr = preprocess_and(expression, variables) elif expression.operator in ( "Or", "Not", ): # Or and Not are not supported by SQLAlchemy translation. raise UnsupportedError(f"{expression.operator}") else: new_expr = preprocess_leaf(expression, variables) return new_expr
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