def preprocess_and( expression: Expression, variables: TGroupedExpressions ) -> Expression: new_expression = [] for expression in expression.args: maybe_expr = preprocess_expression(expression, variables) if maybe_expr: new_expression.append(maybe_expr) return Expression("And", new_expression)
def reflect_expr(expr: Expression): assert expr.operator in COMPARISONS reflections = { "Gt": "Lt", "Geq": "Leq", "Lt": "Gt", "Leq": "Geq", } left, right = expr.args op = expr.operator return Expression(reflections.get(op, op), [right, left])
def sub_var(variable: Variable, value, expression: Expression) -> Expression: """Substitute ``value`` for ``variable`` in ``expression``.""" new_expr = [] for arg in expression.args: if isinstance(arg, Expression): arg = sub_var(variable, value, arg) elif arg == variable: arg = value new_expr.append(arg) return Expression(expression.operator, new_expr)
def preprocess(expression: Expression) -> Expression: # Collect expressions that constrain variables besides _this. variables: TGroupedExpressions = defaultdict(list) new_expr = preprocess_expression(expression, variables) assert new_expr is not None # Join each expression by AND. expressions = {var: Expression("And", args) for var, args in variables.items()} # Subsitute _this for each variable. expressions = { var: sub_this(var, expression) for var, expression in expressions.items() } # Subsitute new expressions for variables in original expression. for var, expr in expressions.items(): new_expr = sub_var(var, expr, preprocess(new_expr)) return new_expr
def translate_compare(expression: Expression, session: Session, model, get_model): (left, right) = expression.args left_path = dot_path(left) right_path = dot_path(right) if left_path[1:]: assert left_path[0] == Variable("_this") assert not right_path path, field_name = left_path[1:-1], left_path[-1] return translate_dot( path, session, model, functools.partial(emit_compare, field_name, right, expression.operator), ) elif right_path and right_path[0] == "_this": return translate_compare( Expression(flip_op(expression.operator), [right, left]), session, model, get_model, ) 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 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. path = dot_path(right) assert path[0] == "_this" path = path[1:] assert path # Partial In: LHS is an expression if isinstance(left, Expression): return translate_dot( path, session, model, functools.partial(emit_subexpression, left, get_model), ) elif isinstance(left, Variable): # A variable with no additional constraints return translate_dot( path, session, model, functools.partial(emit_subexpression, Expression("And", []), get_model), ) else: # Contains: LHS is not an expression. # TODO (dhatch) Missing check, left type must match type of the target? path, field_name = path[:-1], path[-1] return translate_dot( path, session, model, functools.partial(emit_contains, field_name, left))
def test_preprocess_nested_many_many(): _this = Variable("_this") expression = Expression( "And", [ Expression("Isa", [_this, Pattern("Post", {})]), Expression( "In", [Variable("_tag_16"), Expression("Dot", [_this, "tags"])]), Expression( "In", [ Variable("_user_18"), Expression("Dot", [Variable("_tag_16"), "users"]), ], ), Expression( "Unify", [ "admin", Expression("Dot", [Variable("_user_18"), "username"]) ], ), ], ) vars = defaultdict(list) new_expression = preprocess_expression(expression, vars) assert new_expression == Expression( "And", [ Expression("Isa", [_this, Pattern("Post", {})]), Expression( "In", [Variable("_tag_16"), Expression("Dot", [_this, "tags"])]), ], ) assert vars == { Variable("_tag_16"): [ Expression( "In", [ Variable("_user_18"), Expression("Dot", [Variable("_tag_16"), "users"]), ], ) ], Variable("_user_18"): [ Expression( "Unify", [ "admin", Expression("Dot", [Variable("_user_18"), "username"]) ], ) ], } users_expr = Expression("And", [ Expression("Unify", ["admin", Expression("Dot", [_this, "username"])]) ]) tags_expr = Expression( "And", [Expression( "In", [users_expr, Expression("Dot", [_this, "users"])])]) assert preprocess(expression) == Expression( "And", [ Expression("Isa", [_this, Pattern("Post", {})]), Expression("In", [tags_expr, Expression("Dot", [_this, "tags"])]), ], )