def compile_SessionStateDecl( decl: qlast.SessionStateDecl, *, ctx: context.ContextLevel) -> irast.SessionStateCmd: aliases = {} for item in decl.items: if isinstance(item, qlast.ModuleAliasDecl): try: module = ctx.schema.get_module(item.module) except LookupError: raise errors.EdgeQLError( f'module {item.module!r} does not exist', context=item.context ) aliases[item.alias] = module else: raise errors.EdgeQLError( f'expression aliases in SET are not supported yet', context=item.context ) return irast.SessionStateCmd( modaliases=aliases )
def _cmd_tree_from_ast(cls, astnode, context, schema): cmd = super()._cmd_tree_from_ast(astnode, context, schema) if isinstance(astnode, qlast.CreateConcreteConstraint): if astnode.args: args = [] for arg in astnode.args: arg_expr = s_expr.ExpressionText( edgeql.generate_source(arg.arg, pretty=False)) args.append(arg_expr) cmd.add(sd.AlterObjectProperty(property='args', new_value=args)) elif isinstance(astnode, qlast.CreateConstraint): if astnode.args: paramnames, paramdefaults, paramtypes, paramkinds, variadic = \ s_func.parameters_from_ast( astnode, context.modaliases, schema) if variadic is not None: cmd.add( sd.AlterObjectProperty(property='varparam', new_value=variadic)) for pname, pdefault, ptype in zip(paramnames, paramdefaults, paramtypes): if pname is not None: raise ql_errors.EdgeQLError( 'constraints do not support named parameters', context=astnode.context) if pdefault is not None: raise ql_errors.EdgeQLError( 'constraints do not support parameters ' 'with defaults', context=astnode.context) if ptype is None: raise ql_errors.EdgeQLError('untyped parameter', context=astnode.context) cmd.add( sd.AlterObjectProperty(property='paramtypes', new_value=paramtypes)) # 'subject' can be present in either astnode type if astnode.subject: subjectexpr = s_expr.ExpressionText( edgeql.generate_source(astnode.subject, pretty=False)) cmd.add( sd.AlterObjectProperty(property='subjectexpr', new_value=subjectexpr)) cls._validate_subcommands(astnode) return cmd
def _cast_expr(ql_type: qlast.TypeName, ir_expr: irast.Base, *, source_context: parsing.ParserContext, ctx: context.ContextLevel) -> irast.Base: try: orig_type = irutils.infer_type(ir_expr, ctx.schema) except errors.EdgeQLError: # It is possible that the source expression is unresolved # if the expr is an empty set (or a coalesce of empty sets). orig_type = None if isinstance(orig_type, s_types.Tuple): # For tuple-to-tuple casts we generate a new tuple # to simplify things on sqlgen side. new_type = typegen.ql_typeref_to_type(ql_type, ctx=ctx) if not isinstance(new_type, s_types.Tuple): raise errors.EdgeQLError(f'cannot cast tuple to {new_type.name}', context=source_context) if len(orig_type.element_types) != len(new_type.element_types): raise errors.EdgeQLError( f'cannot cast to {new_type.name}: ' f'number of elements is not the same', context=source_context) new_names = list(new_type.element_types) elements = [] for i, n in enumerate(orig_type.element_types): val = setgen.generated_set(irast.TupleIndirection(expr=ir_expr, name=n), ctx=ctx) val.path_id = irutils.tuple_indirection_path_id( ir_expr.path_id, n, orig_type.element_types[n]) val_type = irutils.infer_type(val, ctx.schema) new_el_name = new_names[i] if val_type != new_type.element_types[new_el_name]: # Element cast val = _cast_expr(ql_type.subtypes[i], val, ctx=ctx, source_context=source_context) elements.append(irast.TupleElement(name=new_el_name, val=val)) return irast.Tuple(named=new_type.named, elements=elements) elif isinstance(ir_expr, irast.EmptySet): # For the common case of casting an empty set, we simply # generate a new EmptySet node of the requested type. scls = typegen.ql_typeref_to_type(ql_type, ctx=ctx) return irutils.new_empty_set(ctx.schema, scls=scls, alias=ir_expr.path_id[-1].name.name) else: typ = typegen.ql_typeref_to_ir_typeref(ql_type, ctx=ctx) return irast.TypeCast(expr=ir_expr, type=typ)
def __infer_array(ir, schema): if ir.elements: element_type = _infer_common_type(ir.elements, schema) if element_type is None: raise ql_errors.EdgeQLError('could not determine array type', context=ir.context) else: raise ql_errors.EdgeQLError('could not determine type of empty array', context=ir.context) return s_types.Array(element_type=element_type)
def __infer_map(ir, schema): if not ir.keys: raise ql_errors.EdgeQLError('could not determine type of empty map', context=ir.context) key_type = _infer_common_type(ir.keys, schema) if key_type is None: raise ql_errors.EdgeQLError('could not determine map keys type', context=ir.context) element_type = _infer_common_type(ir.values, schema) if element_type is None: raise ql_errors.EdgeQLError('could not determine map values type', context=ir.context) return s_types.Map(key_type=key_type, element_type=element_type)
def compile_UpdateQuery( expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: with ctx.subquery() as ictx: stmt = irast.UpdateStmt() init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) subject = dispatch.compile(expr.subject, ctx=ictx) subj_type = irutils.infer_type(subject, ictx.schema) if not isinstance(subj_type, s_objtypes.ObjectType): raise errors.EdgeQLError( f'cannot update non-ObjectType objects', context=expr.subject.context ) stmt.subject = compile_query_subject( subject, shape=expr.shape, view_rptr=ctx.view_rptr, compile_views=True, result_alias=expr.subject_alias, is_update=True, ctx=ictx) stmt.result = setgen.class_set( stmt.subject.scls.material_type(), ctx=ctx) stmt.where = clauses.compile_where_clause( expr.where, ctx=ictx) result = fini_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) return result
def enforce_singleton(expr: irast.Base, *, ctx: context.ContextLevel) -> None: cardinality = infer_cardinality(expr, ctx=ctx) if cardinality != irast.Cardinality.ONE: raise errors.EdgeQLError( 'possibly more than one element returned by an expression ' 'where only singletons are allowed', context=expr.context)
def __infer_binop(ir, schema): left_type, right_type = _infer_binop_args(ir.left, ir.right, schema) if isinstance(ir.op, (ast.ops.ComparisonOperator, ast.ops.TypeCheckOperator, ast.ops.MembershipOperator)): result = schema.get('std::bool') else: result = s_basetypes.TypeRules.get_result(ir.op, (left_type, right_type), schema) if result is None: result = s_basetypes.TypeRules.get_result( (ir.op, 'reversed'), (right_type, left_type), schema) if result is None: if right_type.implicitly_castable_to(left_type, schema): right_type = left_type elif left_type.implicitly_castable_to(right_type, schema): left_type = right_type result = s_basetypes.TypeRules.get_result( (ir.op, 'reversed'), (right_type, left_type), schema) if result is None: raise ql_errors.EdgeQLError( f'binary operator `{ir.op.upper()}` is not defined for types ' f'{left_type.name} and {right_type.name}', context=ir.left.context) return result
def compile_IfElse(expr: qlast.IfElse, *, ctx: context.ContextLevel) -> irast.Base: condition = setgen.ensure_set(dispatch.compile(expr.condition, ctx=ctx), ctx=ctx) ql_if_expr = expr.if_expr ql_else_expr = expr.else_expr with ctx.newscope(fenced=True) as scopectx: if_expr = dispatch.compile(ql_if_expr, ctx=scopectx) with ctx.newscope(fenced=True) as scopectx: else_expr = dispatch.compile(ql_else_expr, ctx=scopectx) if_expr_type = irutils.infer_type(if_expr, ctx.schema) else_expr_type = irutils.infer_type(else_expr, ctx.schema) result = s_utils.get_class_nearest_common_ancestor( [if_expr_type, else_expr_type]) if result is None: raise errors.EdgeQLError( 'if/else clauses must be of related types, got: {}/{}'.format( if_expr_type.name, else_expr_type.name), context=expr.context) return setgen.generated_set(irast.IfElseExpr(if_expr=if_expr, else_expr=else_expr, condition=condition), ctx=ctx)
def compile_DeleteQuery( expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: with ctx.subquery() as ictx: stmt = irast.DeleteStmt() init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) # DELETE Expr is a delete(SET OF X), so we need a scope fence. with ictx.newscope(fenced=True) as scopectx: subject = setgen.scoped_set( dispatch.compile(expr.subject, ctx=scopectx), ctx=scopectx) subj_type = irutils.infer_type(subject, ictx.schema) if not isinstance(subj_type, s_objtypes.ObjectType): raise errors.EdgeQLError( f'cannot delete non-ObjectType objects', context=expr.subject.context ) stmt.subject = compile_query_subject( subject, shape=None, result_alias=expr.subject_alias, ctx=ictx) stmt.result = setgen.class_set( stmt.subject.scls.material_type(), ctx=ctx) stmt.result.path_id = stmt.subject.path_id result = fini_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx) return result
def __infer_struct_indirection(ir, schema): struct_type = infer_type(ir.expr, schema) result = struct_type.element_types.get(ir.name) if result is None: raise ql_errors.EdgeQLError('could not determine struct element type', context=ir.context) return result
def __infer_coalesce(ir, schema): result = _infer_common_type([ir.left, ir.right], schema) if result is None: raise ql_errors.EdgeQLError( 'coalescing operator must have operands of related types', context=ir.context) return result
def get_schema_object( name: typing.Union[str, qlast.ObjectRef], module: typing.Optional[str] = None, *, item_types: typing.Optional[typing.List[s_obj.ObjectMeta]], ctx: context.ContextLevel, srcctx: typing.Optional[parsing.ParserContext] = None) -> s_obj.Object: if isinstance(name, qlast.ObjectRef): if srcctx is None: srcctx = name.context module = name.module name = name.name if module: name = sn.Name(name=name, module=module) if not module: result = ctx.aliased_views.get(name) if result is not None: return result try: scls = ctx.schema.get(name=name, module_aliases=ctx.modaliases, type=item_types) except s_err.ItemNotFoundError as e: qlerror = qlerrors.EdgeQLError(e.args[0], context=srcctx) s_utils.enrich_schema_lookup_error(qlerror, name, modaliases=ctx.modaliases, schema=ctx.schema, item_types=item_types) raise qlerror except s_err.SchemaError as e: raise qlerrors.EdgeQLError(e.args[0], context=srcctx) result = ctx.aliased_views.get(scls.name) if result is None: result = scls return result
def compile_TypeFilter(expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: # Expr[IS Type] expressions. with ctx.new() as scopectx: arg = setgen.ensure_set(dispatch.compile(expr.expr, ctx=scopectx), ctx=scopectx) arg_type = irutils.infer_type(arg, ctx.schema) if not isinstance(arg_type, s_objtypes.ObjectType): raise errors.EdgeQLError( f'invalid type filter operand: {arg_type.name} ' f'is not an object type', context=expr.expr.context) typ = schemactx.get_schema_type(expr.type.maintype, ctx=ctx) if not isinstance(typ, s_objtypes.ObjectType): raise errors.EdgeQLError( f'invalid type filter operand: {typ.name} is not an object type', context=expr.type.context) return setgen.class_indirection_set(arg, typ, optional=False, ctx=ctx)
def infer_type(ir, schema): try: return ir._inferred_type_ except AttributeError: pass result = _infer_type(ir, schema) if (result is not None and not isinstance(result, (s_obj.Object, s_obj.ObjectMeta))): raise ql_errors.EdgeQLError( f'infer_type({ir!r}) retured {result!r} instead of a Object', context=ir.context) if result is None or result.name == 'std::any': raise ql_errors.EdgeQLError('could not determine expression type', context=ir.context) ir._inferred_type_ = result return result
def __infer_index(ir, schema): node_type = infer_type(ir.expr, schema) index_type = infer_type(ir.index, schema) str_t = schema.get('std::str') int_t = schema.get('std::int64') result = None if node_type.issubclass(str_t): if not index_type.issubclass(int_t): raise ql_errors.EdgeQLError( f'cannot index string by {index_type.name}, ' f'{int_t.name} was expected', context=ir.index.context) result = str_t elif isinstance(node_type, s_types.Map): if not index_type.issubclass(node_type.key_type): raise ql_errors.EdgeQLError( f'cannot index {node_type.name} by {index_type.name}, ' f'{node_type.key_type.name} was expected', context=ir.index.context) result = node_type.element_type elif isinstance(node_type, s_types.Array): if not index_type.issubclass(int_t): raise ql_errors.EdgeQLError( f'cannot index array by {index_type.name}, ' f'{int_t.name} was expected', context=ir.index.context) result = node_type.element_type return result
def compile_FunctionCall(expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: with ctx.new() as fctx: if isinstance(expr.func, str): funcname = expr.func else: funcname = sn.Name(expr.func[1], expr.func[0]) funcs = fctx.schema.get_functions(funcname, module_aliases=fctx.modaliases) if funcs is None: raise errors.EdgeQLError( f'could not resolve function name {funcname}', context=expr.context) fctx.in_func_call = True args, kwargs, arg_types = process_func_args(expr, funcname, ctx=fctx) for funcobj in funcs: if check_function(funcobj, arg_types): break else: raise errors.EdgeQLError( f'could not find a function variant {funcname}', context=expr.context) fixup_param_scope(funcobj, args, kwargs, ctx=fctx) node = irast.FunctionCall(func=funcobj, args=args, kwargs=kwargs) if funcobj.initial_value is not None: rtype = irutils.infer_type(node, fctx.schema) iv_ql = qlast.TypeCast(expr=qlparser.parse_fragment( funcobj.initial_value), type=typegen.type_to_ql_typeref(rtype)) node.initial_value = dispatch.compile(iv_ql, ctx=fctx) ir_set = setgen.ensure_set(node, ctx=ctx) return ir_set
def _add_to_schema(self, schema): props = super().get_struct_properties(schema) fullname = self._get_function_fullname(props['name'], props.get('paramtypes')) func = schema.get(fullname, None) if func: raise ql_errors.EdgeQLError( f'Cannot create a function {self.classname}: ' f'a function with the same signature ' f'is already defined', context=self.source_context) super()._add_to_schema(schema)
def __infer_unaryop(ir, schema): result = None operand_type = infer_type(ir.expr, schema) if ir.op == ast.ops.NOT: if operand_type.name == 'std::bool': result = operand_type else: if ir.op not in {ast.ops.UPLUS, ast.ops.UMINUS}: raise ql_errors.EdgeQLError(f'unknown unary operator: {ir.op}', context=ir.context) result = s_basetypes.TypeRules.get_result(ir.op, (operand_type, ), schema) if result is None: raise ql_errors.EdgeQLError( f'unary operator `{ir.op.upper()}` is not defined ' f'for type {operand_type.name}', context=ir.context) return result
def __infer_ifelse(ir, schema): if_expr_type = infer_type(ir.if_expr, schema) else_expr_type = infer_type(ir.else_expr, schema) result = s_utils.get_class_nearest_common_ancestor( [if_expr_type, else_expr_type]) if result is None: raise ql_errors.EdgeQLError( 'if/else clauses must be of related types, got: {}/{}'.format( if_expr_type.name, else_expr_type.name), context=ir.if_expr.context) return result
def _infer_common_type(irs: typing.List[irast.Base], schema): if not irs: raise ql_errors.EdgeQLError( 'cannot determine common type of an empty set', context=irs[0].context) col_type = None arg_types = [] empties = [] for i, arg in enumerate(irs): if isinstance(arg, irast.EmptySet) and arg.scls is None: empties.append(i) continue arg_type = infer_type(arg, schema) arg_types.append(arg_type) if isinstance(arg_type, s_types.Collection): col_type = arg_type if not arg_types: raise ql_errors.EdgeQLError( 'cannot determine common type of an empty set', context=irs[0].context) if col_type is not None: if not all(col_type.issubclass(t) for t in arg_types): raise ql_errors.EdgeQLError('cannot determine common type', context=irs[0].context) common_type = col_type else: common_type = s_utils.get_class_nearest_common_ancestor(arg_types) for i in empties: amend_empty_set_type(irs[i], common_type, schema) return common_type
def process_func_args( expr: qlast.FunctionCall, funcname: sn.Name, *, ctx: context.ContextLevel) \ -> typing.Tuple[ typing.List[irast.Base], # args typing.Dict[str, irast.Base], # kwargs typing.List[s_types.Type]]: # arg_types args = [] kwargs = {} arg_types = [] for ai, a in enumerate(expr.args): arg_ql = a.arg if a.sort or a.filter: arg_ql = astutils.ensure_qlstmt(arg_ql) if a.filter: arg_ql.where = astutils.extend_qlbinop(arg_ql.where, a.filter) if a.sort: arg_ql.orderby = a.sort + arg_ql.orderby with ctx.newscope(fenced=True) as fencectx: # We put on a SET OF fence preemptively in case this is # a SET OF arg, which we don't know yet due to polymorphic # matching. arg = setgen.scoped_set(dispatch.compile(arg_ql, ctx=fencectx), ctx=fencectx) if a.name: kwargs[a.name] = arg aname = a.name else: args.append(arg) aname = ai arg_type = irutils.infer_type(arg, ctx.schema) if arg_type is None: raise errors.EdgeQLError( f'could not resolve the type of argument ' f'${aname} of function {funcname}', context=a.context) arg_types.append(arg_type) return args, kwargs, arg_types
def ensure_set(expr: irast.Base, *, typehint: typing.Optional[s_types.Type] = None, path_id: typing.Optional[irast.PathId] = None, ctx: context.ContextLevel) -> irast.Set: if not isinstance(expr, irast.Set): expr = generated_set(expr, typehint=typehint, path_id=path_id, ctx=ctx) if (isinstance(expr, irast.EmptySet) and expr.scls is None and typehint is not None): irutils.amend_empty_set_type(expr, typehint, schema=ctx.schema) if typehint is not None and not expr.scls.issubclass(typehint): raise errors.EdgeQLError( f'expecting expression of type {typehint.name}, ' f'got {expr.scls.name}', context=expr.context) return expr
def _infer_binop_args(left, right, schema): if not isinstance(left, irast.EmptySet) or left.scls is not None: left_type = infer_type(left, schema) else: left_type = None if not isinstance(right, irast.EmptySet) or right.scls is not None: right_type = infer_type(right, schema) else: right_type = None if left_type is None and right_type is None: raise ql_errors.EdgeQLError( 'cannot determine the type of an empty set', context=left.context) elif left_type is None: amend_empty_set_type(left, right_type, schema) left_type = right_type elif right_type is None: amend_empty_set_type(right, left_type, schema) right_type = left_type return left_type, right_type
def infer_cardinality(ir, singletons, schema): try: return ir._inferred_cardinality_[frozenset(singletons)] except (AttributeError, KeyError): pass result = _infer_cardinality(ir, singletons, schema) if result not in {ONE, MANY}: raise ql_errors.EdgeQLError( 'could not determine the cardinality of ' 'set produced by expression', context=ir.context) try: cache = ir._inferred_cardinality_ except AttributeError: cache = ir._inferred_cardinality_ = {} cache[frozenset(singletons)] = result return result
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_nodes.Node, *, path_id: irast.PathId, is_insert: bool = False, is_update: bool = False, view_rptr: typing.Optional[context.ViewRPtr] = None, ctx: context.CompilerContext) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. is_polymorphic = len(steps) == 2 scls = view_scls.peel_view() ptrsource = scls qlexpr = None if is_polymorphic: source = qlast.TypeFilter(expr=qlast.Path(steps=[qlast.Source()]), type=qlast.TypeName(maintype=steps[0])) lexpr = steps[1] ptrsource = schemactx.get_schema_type(steps[0], ctx=ctx) elif len(steps) == 1: lexpr = steps[0] is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None: raise errors.EdgeQLError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) ptrsource = scls = view_rptr.ptrcls source = qlast.Source() else: raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') ptrname = (lexpr.ptr.module, lexpr.ptr.name) ptrcls_is_derived = False compexpr = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Nested insert short form: # INSERT Foo { bar: Spam { name := 'name' }} # Expand to: # INSERT Foo { bar := (INSERT Spam { name := 'name' }) } if lexpr.target is not None: ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx) else: ptr_target = None base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, target=ptr_target, ctx=ctx) compexpr = qlast.InsertQuery(subject=qlast.Path(steps=[ qlast.ObjectRef(name=ptrcls.target.name.name, module=ptrcls.target.name.module) ]), shape=shape_el.elements) if compexpr is None: if lexpr.target is not None: ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx) else: ptr_target = None base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, target=ptr_target, ctx=ctx) base_ptr_is_computable = ptrcls in ctx.source_map if ptr_target is not None and ptr_target != base_ptrcls.target: # This happens when a union type target is narrowed by an # [IS Type] construct. Since the derived pointer will have # the correct target, we don't need to do anything, but # remove the [IS] qualifier to prevent recursion. lexpr.target = None else: ptr_target = ptrcls.target ptr_cardinality = ptrcls.cardinality if shape_el.elements: sub_view_rptr = context.ViewRPtr(view_scls, ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = path_id.extend(ptrcls, target=ptrcls.target) ctx.path_scope.attach_path(sub_path_id) if is_update: for subel in shape_el.elements or []: is_prop = (isinstance(subel.expr.steps[0], qlast.Ptr) and subel.expr.steps[0].type == 'property') if not is_prop: raise errors.EdgeQLError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view(scls=ptr_target, path_id=sub_path_id, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, ctx=ctx) else: ptr_target = _process_view(scls=ptr_target, path_id=sub_path_id, view_rptr=sub_view_rptr, elements=shape_el.elements, ctx=ctx) ptrcls = sub_view_rptr.derived_ptrcls if ptrcls is None: ptrcls_is_derived = False ptrcls = sub_view_rptr.ptrcls else: ptrcls_is_derived = True if (shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic): if qlexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) qlexpr = astutils.ensure_qlstmt(qlexpr) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit else: try: base_ptrcls = ptrcls = setgen.resolve_ptr( ptrsource, ptrname, s_pointers.PointerDirection.Outbound, ctx=ctx) except errors.EdgeQLReferenceError: if is_mutation: raise ptr_module = (ptrname[0] or ctx.derived_target_module or scls.name.module) if is_linkprop: ptr_metacls = s_props.Property else: ptr_metacls = s_links.Link ptr_name = sn.SchemaName(module=ptr_module, name=ptrname[1]) base_ptrcls = ptrcls = ptr_metacls(name=ptr_name) qlexpr = astutils.ensure_qlstmt(compexpr) with ctx.newscope(fenced=True) as shape_expr_ctx: # Put current pointer class in context, so # that references to link properties in sub-SELECT # can be resolved. This is necessary for proper # evaluation of link properties on computable links, # most importantly, in INSERT/UPDATE context. shape_expr_ctx.view_rptr = context.ViewRPtr(view_scls, ptrcls, is_insert=is_insert, is_update=is_update) shape_expr_ctx.path_scope.unnest_fence = True if is_mutation: shape_expr_ctx.expr_exposed = True irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls if derived_ptrcls is not None: ptrcls_is_derived = True ptrcls = derived_ptrcls inferred_cardinality = pathctx.infer_cardinality(irexpr, ctx=ctx) if inferred_cardinality == irast.Cardinality.MANY: ptr_cardinality = s_pointers.PointerCardinality.ManyToMany else: ptr_cardinality = s_pointers.PointerCardinality.ManyToOne ptr_target = irutils.infer_type(irexpr, ctx.schema) if ptr_target is None: msg = 'cannot determine expression result type' raise errors.EdgeQLError(msg, context=shape_el.context) if is_mutation and not ptr_target.assignment_castable_to( base_ptrcls.target, schema=ctx.schema): # Validate that the insert/update expression is # of the correct class. lname = f'({ptrsource.name}).{ptrcls.shortname.name}' expected = [repr(str(base_ptrcls.target.name))] raise edgedb_error.InvalidPointerTargetError( f'invalid target for link {str(lname)!r}: ' f'{str(ptr_target.name)!r} (expecting ' f'{" or ".join(expected)})') if is_mutation and base_ptrcls.singular(): pathctx.enforce_singleton(irexpr, ctx=ctx) if qlexpr is not None or ptr_target is not ptrcls.target: if not ptrcls_is_derived: if ptrcls.is_link_property(): rptrcls = view_rptr.derived_ptrcls if rptrcls is None: rptrcls = schemactx.derive_view( view_rptr.ptrcls, view_rptr.source, view_scls, is_insert=view_rptr.is_insert, is_update=view_rptr.is_update, ctx=ctx) view_rptr.derived_ptrcls = rptrcls src_scls = rptrcls else: src_scls = view_scls ptrcls = schemactx.derive_view(ptrcls, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name_quals=[view_scls.name], ctx=ctx) if qlexpr is not None: ctx.source_map[ptrcls] = (qlexpr, ctx) ptrcls.computable = True if not is_mutation: ptrcls.cardinality = ptr_cardinality if ptrcls.shortname == 'std::__type__' and qlexpr is not None: msg = 'cannot assign to __type__' raise errors.EdgeQLError(msg, context=shape_el.context) return ptrcls
def compile_EmptyCollection(expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base: raise errors.EdgeQLError(f'could not determine type of empty collection', context=expr.context)
def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set: """Create an ir.Set representing the given EdgeQL path expression.""" anchors = ctx.anchors path_tip = None if expr.partial: if ctx.partial_path_prefix is not None: path_tip = ctx.partial_path_prefix else: raise errors.EdgeQLError('could not resolve partial path ', context=expr.context) extra_scopes = {} computables = [] path_sets = [] for i, step in enumerate(expr.steps): if isinstance(step, qlast.Source): # 'self' can only appear as the starting path label # syntactically and is a known anchor path_tip = anchors.get(step.__class__) elif isinstance(step, qlast.Subject): # '__subject__' can only appear as the starting path label # syntactically and is a known anchor path_tip = anchors.get(step.__class__) elif isinstance(step, qlast.ObjectRef): if i > 0: raise RuntimeError( 'unexpected ObjectRef as a non-first path item') refnode = None if not step.module: # Check if the starting path label is a known anchor refnode = anchors.get(step.name) if refnode is not None: path_tip = copy.copy(refnode) else: scls = schemactx.get_schema_type( step, item_types=(s_objtypes.ObjectType, ), ctx=ctx) if (scls.view_type is not None and scls.name not in ctx.view_nodes): # This is a schema-level view, as opposed to # a WITH-block or inline alias view. scls = stmtctx.declare_view_from_schema(scls, ctx=ctx) path_tip = class_set(scls, ctx=ctx) view_set = ctx.view_sets.get(scls) if view_set is not None: path_tip = new_set_from_set(view_set, ctx=ctx) path_scope = ctx.path_scope_map.get(view_set) extra_scopes[path_tip] = path_scope.copy() view_scls = ctx.class_view_overrides.get(scls.name) if view_scls is not None: path_tip.scls = view_scls elif isinstance(step, qlast.Ptr): # Pointer traversal step ptr_expr = step ptr_target = None direction = (ptr_expr.direction or s_pointers.PointerDirection.Outbound) if ptr_expr.target: # ... link [IS Target] ptr_target = schemactx.get_schema_type(ptr_expr.target, ctx=ctx) if not isinstance(ptr_target, s_objtypes.ObjectType): raise errors.EdgeQLError( f'invalid type filter operand: {ptr_target.name} ' f'is not an object type', context=ptr_expr.target.context) ptr_name = (ptr_expr.ptr.module, ptr_expr.ptr.name) if ptr_expr.type == 'property': # Link property reference; the source is the # link immediately preceding this step in the path. source = path_tip.rptr.ptrcls else: source = path_tip.scls with ctx.newscope(fenced=True, temporary=True) as subctx: if isinstance(source, s_types.Tuple): path_tip = tuple_indirection_set( path_tip, source=source, ptr_name=ptr_name, source_context=step.context, ctx=subctx) else: path_tip = ptr_step_set(path_tip, source=source, ptr_name=ptr_name, direction=direction, ptr_target=ptr_target, ignore_computable=True, source_context=step.context, ctx=subctx) ptrcls = path_tip.rptr.ptrcls if _is_computable_ptr(ptrcls, ctx=ctx): computables.append(path_tip) else: # Arbitrary expression if i > 0: raise RuntimeError( 'unexpected expression as a non-first path item') with ctx.newscope(fenced=True, temporary=True) as subctx: path_tip = ensure_set(dispatch.compile(step, ctx=subctx), ctx=subctx) if path_tip.path_id.is_type_indirection_path(): scope_set = path_tip.rptr.source else: scope_set = path_tip extra_scopes[scope_set] = subctx.path_scope mapped = ctx.view_map.get(path_tip.path_id) if mapped is not None: path_tip = new_set(path_id=mapped.path_id, scls=path_tip.scls, expr=mapped.expr, ctx=ctx) path_sets.append(path_tip) path_tip.context = expr.context pathctx.register_set_in_scope(path_tip, ctx=ctx) for ir_set in computables: scope = ctx.path_scope.find_descendant(ir_set.path_id) if scope is None: # The path is already in the scope, no point # in recompiling the computable expression. continue with ctx.new() as subctx: subctx.path_scope = scope comp_ir_set = computable_ptr_set(ir_set.rptr, ctx=subctx) i = path_sets.index(ir_set) if i != len(path_sets) - 1: path_sets[i + 1].rptr.source = comp_ir_set else: path_tip = comp_ir_set path_sets[i] = comp_ir_set for ir_set, scope in extra_scopes.items(): node = ctx.path_scope.find_descendant(ir_set.path_id) if node is None: # The path portion not being a descendant means # that is is already present in the scope above us, # along with the view scope. continue fuse_scope_branch(ir_set, node, scope, ctx=ctx) if ir_set.path_scope_id is None: pathctx.assign_set_scope(ir_set, node, ctx=ctx) return path_tip
def _get_view_expr(cls, astnode): expr = cls._maybe_get_view_expr(astnode) if expr is None: raise ql_errors.EdgeQLError( f'Missing required view expression', context=astnode.context) return expr