def serialize_project(node, context): return IR.select( node.kind_name, filters=IR.filter( IR.attribute(None, "name"), IR.literal(node.name), "=" ), limit=1, )
def compile_constant(node, state): expr = IR.literal(node.value) # Constants are represented as repr(obj) in the # serialization part, so we have to re-cast it. if state.match == "Constant": expr.value = repr(expr.value) return IR.filter(state.compute_path(), expr, "=")
def compile_sequence(node, state): total_length = len(node.items) verify_call = IR.call("count", [state.compute_path()]) length_verifier = IR.filter(verify_call, total_length, "=") if total := node.items.count(grammar.Expand): state.ensure(node, total == 1) length_verifier = IR.filter(verify_call, total_length - 1, ">=")
def apply_ast(node, context): with context.enter_node(node): insertions = { field: serialize(value, context) for field, value in iter_properties(node) if value is not None } query = IR.insert(node.kind_name, insertions) return context.connection.query_one(IR.construct(query))
def serialize_ast(node, context): if node.is_enum: # <ast::op>'Add' return IR.enum_member(node.base_name, node.kind_name) else: # (INSERT ast::BinOp {.left := ..., ...}) reference = apply_ast(node, context) context.new_reference(reference.id) # (SELECT ast::expr FILTER .id = ... LIMIT 1) return IR.select( node.base_name, filters=IR.object_ref(reference), limit=1 )
def metadata_parent(parent_node, state): state.ensure(parent_node, len(parent_node.filters) == 1) parent_field, filter_value = parent_node.filters.popitem() state.ensure(parent_node, filter_value is grammar.Ignore) with state.temp_pointer("_parent_types"): return IR.filter( IR.tuple( [parent_node.bound_node.type_id, IR.literal(parent_field)]), state.compute_path(), "IN", )
def compile_reference(node, state): obtained_type = state.field_info.type if pointer := state.scope.lookup(node.name): expected_type = pointer.field_info.type state.ensure(node, expected_type is obtained_type) left = state.compute_path() right = pointer.compute_path() if issubclass(expected_type, ast.expr): left = IR.attribute(left, "_tag") right = IR.attribute(right, "_tag") return IR.filter(left, right, "=")
def convert_meta(node, state, arguments): state.ensure(node, state.pointer == "__metadata__") filters = None if arguments.parent: filters = IR.combine_filters(filters, metadata_parent(arguments.parent, state)) return filters
def aggregate_array(state): # If we are in a nested list search (e.g: Call(args=[Call(args=[Name()])])) # we can't directly use `ORDER BY @index` since the EdgeDB can't quite infer # which @index are we talking about. if len(state.parents) >= 1: path = IR.attribute( IR.typed(IR.name(_COMPILER_WORKAROUND_FOR_TARGET), state.match), state.pointer, ) body = IR.loop( IR.name(_COMPILER_WORKAROUND_FOR_TARGET), state.parents[-1].compute_path(allow_missing=True), IR.select(path, order=IR.property("index")), ) else: body = IR.select(state.compute_path(), order=IR.property("index")) return IR.call("array_agg", [body])
def compute_path(self, allow_missing=False): parent, *parents = self.get_ordered_parents() def get_pointer(state, allow_missing): pointer = state.pointer if allow_missing: pointer = IR.optional(pointer) return pointer base = get_pointer(parent, allow_missing) if not parent.is_flag_set("in for loop"): base = IR.attribute(None, base) for parent in parents: base = IR.typed(base, parent.match) base = IR.attribute(base, get_pointer(parent, allow_missing)) return base
def run_query_on_connection( connection, reiz_ql, *, limit=DEFAULT_LIMIT, offset=0, ): query = IR.construct(compile_query(reiz_ql, limit, offset)) query_set = connection.query(query) return process_queryset(query_set)
def compile_matcher(node, state): if state is None: state = CompilerState(node.name) else: state = CompilerState.from_parent(node.name, state) filters = None for key, value in node.filters.items(): if value is grammar.Ignore: continue if right_filter := state.compile(key, value): filters = IR.combine_filters(filters, right_filter)
async def run_query_on_async_connection( connection, reiz_ql, *, limit=DEFAULT_LIMIT, offset=0, loop=None, timeout=config.web.timeout, ): query = IR.construct(compile_query(reiz_ql, limit, offset)) query_set = await asyncio.wait_for(connection.query(query), timeout=timeout, loop=loop) return process_queryset(query_set)
def compile_query(self): query = compile_query(self.reiz_ql, limit=None, offset=0) query.filters = IR.combine_filters( query.filters, IR.filter( IR.attribute(IR.attribute(None, "_module"), "filename"), IR.literal(self.expected_filename), "=", ), ) return IR.construct(query)
async def analyze_query(request): if "query" not in request.json: return error("Missing 'query' data") results = dict.fromkeys(("exception", "reiz_ql", "edge_ql")) try: reiz_ql = parse_query(request.json["query"]) results["reiz_ql"] = normalize(asdict(reiz_ql)) results["edge_ql"] = IR.construct(compile_to_ir(reiz_ql)) except ReizQLSyntaxError as syntax_err: results["status"] = "error" results["exception"] = syntax_err.message results.update(syntax_err.position) else: results["status"] = "success" return json_response(results)
def main(): parser = ArgumentParser() parser.add_argument( "source", type=FileType(mode="rb"), nargs="?", default="-", help="the file to parse; defaults to stdin", ) parser.add_argument( "--do-not-optimize", action="store_false", help="do not generated optimized IR", ) options = parser.parse_args() with options.source: query = parse_query(options.source.read()) pprint(query) ir = compile_to_ir(query) print( IR.construct(ir, optimize=options.do_not_optimize, top_level=True))
def convert_length(node, state, arguments): state.ensure(node, any((arguments.min, arguments.max))) count = IR.call("count", [state.compute_path()]) filters = None for value, operator in [ (arguments.min, IR.as_operator(">=")), (arguments.max, IR.as_operator("<=")), ]: if value is None: continue state.ensure(value, isinstance(value, grammar.Constant)) state.ensure(value, isinstance(value.value, int)) filters = IR.combine_filters( filters, IR.filter(count, IR.literal(value.value), operator)) assert filters is not None return filters
def convert_match_enum(node, state): expr = IR.enum_member(node.base, node.name) return IR.filter(state.compute_path(), expr, "=")
def convert_all_any(node, state, arguments): return IR.call(node.name.lower(), [state.codegen(arguments.value)])
def serialize_string(value, context): return IR.literal(value)
def serialize_tuple(sequence, context): return IR.tuple([serialize(value, context) for value in sequence])
def serialize_sequence(sequence, context): ir_set = IR.set([serialize(value, context) for value in sequence]) if all(isinstance(item, _BASIC_SET_TYPES) for item in sequence): # {1, 2, 3} / {<ast::op>'Add', <ast::op>'Sub', ...} return ir_set else: # Inserting a sequence of AST objects would require special # attention to calculate the index property. target = IR.name("item") scope = IR.namespace({"items": ir_set}) loop = IR.loop( target, IR.call("enumerate", [IR.name("items")]), IR.select( IR.attribute(target, 1), selections=[ IR.assign(IR.property("index"), IR.attribute(target, 0)) ], ), ) return IR.add_namespace(scope, loop)
def sync(self, connection): query_set = connection.query(IR.construct_prepared("module.filenames")) self.files = {module.filename for module in query_set} query_set = connection.query(IR.construct_prepared("project.names")) self.projects = {project.name for project in query_set}
if state is None: state = CompilerState(node.name) else: state = CompilerState.from_parent(node.name, state) filters = None for key, value in node.filters.items(): if value is grammar.Ignore: continue if right_filter := state.compile(key, value): filters = IR.combine_filters(filters, right_filter) if state.is_root: state.scope.exit() if state_filters := IR.unpack_filters(state.filters): filters = IR.combine_filters(filters, state_filters) if state.variables: namespace = IR.namespace(state.variables) filters = IR.add_namespace(namespace, IR.select(filters)) return IR.select(state.match, filters=filters) if filters is None: filters = IR.filter(state.parents[-1].compute_path(), IR.wrap(state.match), "IS") return filters @codegen.register(grammar.MatchEnum) def convert_match_enum(node, state):
from reiz.serialization.statistics import Insertion, Statistics from reiz.utilities import _available_cores, guarded, logger @guarded(Insertion.FAILED, ignored_exceptions=(InternalDatabaseError, )) def insert_file(context): if context.is_cached(): return Insertion.CACHED if not (tree := context.as_ast()): return Insertion.SKIPPED with context.connection.transaction(): module = apply_ast(tree, context) module_select = IR.select(tree.kind_name, filters=IR.object_ref(module), limit=1) update_filter = IR.filter( IR.attribute(None, "id"), IR.call("array_unpack", [IR.cast("array<uuid>", IR.variable("ids"))]), "IN", ) for base_type in Schema.module_annotated_types: update = IR.update( base_type.kind_name, filters=update_filter, assignments={"_module": module_select}, ) context.connection.query(IR.construct(update),
def convert_logical_operator(node, state): if node is grammar.LogicOperator.OR: return IR.as_operator("OR") elif node is grammar.LogicOperator.AND: return IR.as_operator("AND")
def convert_logical_operation(node, state): return IR.filter( state.codegen(node.left), state.codegen(node.right), state.codegen(node.operator), )
def compile_operator_flip(node, state): return IR.negate(state.codegen(node.value))
def compile_match_string(node, state): expr = IR.literal(node.value) return IR.filter(state.compute_path(), expr, "LIKE")
def convert_none(node, state): return IR.negate(IR.exists(state.compute_path()))