def compile_expr_field(self, schema, context, field, value): from . import sources as s_sources if field.name in {'default', 'expr'}: singletons = [] path_prefix_anchor = None anchors = {} if field.name == 'expr': parent_ctx = context.get_ancestor( s_sources.SourceCommandContext, self) source_name = parent_ctx.op.classname source = schema.get(source_name, default=None) anchors[qlast.Source().name] = source if not isinstance(source, Pointer): singletons = [source] path_prefix_anchor = qlast.Source().name return type(value).compiled( value, schema=schema, modaliases=context.modaliases, parent_object_type=self.get_schema_metaclass(), anchors=anchors, path_prefix_anchor=path_prefix_anchor, singletons=singletons, ) else: return super().compile_expr_field(schema, context, field, value)
def _inline_type_computable( ir_set: irast.Set, stype: s_objtypes.ObjectType, compname: str, propname: str, *, shape_ptrs: List[ShapePtr], ctx: context.ContextLevel, ) -> None: assert isinstance(stype, s_objtypes.ObjectType) ptr: Optional[s_pointers.Pointer] try: ptr = setgen.resolve_ptr(stype, compname, track_ref=None, ctx=ctx) # The pointer might exist on the base type. That doesn't count, # and we need to re-inject it. if not ptr.get_computable(ctx.env.schema): ptr = None except errors.InvalidReferenceError: ptr = None if ptr is None: ql = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef(name=compname), direction=s_pointers.PointerDirection.Outbound, )], ), compexpr=qlast.Path( steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name='__type__'), direction=s_pointers.PointerDirection.Outbound, ), qlast.Ptr( ptr=qlast.ObjectRef(name=propname), direction=s_pointers.PointerDirection.Outbound, ) ] ) ) with ctx.newscope(fenced=True) as scopectx: scopectx.anchors = scopectx.anchors.copy() scopectx.anchors[qlast.Source().name] = ir_set ptr = _normalize_view_ptr_expr( ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} if ptr not in view_shape_ptrs: view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN))
def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, ) -> s_expr.Expression: from . import sources as s_sources if field.name in {'default', 'expr'}: singletons: List[s_types.Type] = [] path_prefix_anchor = None anchors: Dict[str, Any] = {} if field.name == 'expr': # type ignore below, because the class is used as mixin parent_ctx = context.get_ancestor( s_sources.SourceCommandContext, # type: ignore self ) assert parent_ctx is not None assert isinstance(parent_ctx.op, sd.ObjectCommand) source_name = parent_ctx.op.classname source = schema.get(source_name, default=None) anchors[qlast.Source().name] = source if not isinstance(source, Pointer): assert source is not None singletons = [source] path_prefix_anchor = qlast.Source().name return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors=anchors, path_prefix_anchor=path_prefix_anchor, singletons=frozenset(singletons), ), ) else: return super().compile_expr_field(schema, context, field, value)
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[Pointer]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) source_name = parent_ctx.op.classname source = schema.get(source_name) expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=[source], ) base = None target = expr.irast.stype result_expr = expr.irast.expr.expr if (isinstance(result_expr, irast.SelectStmt) and result_expr.result.rptr is not None): expr_rptr = result_expr.result.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound) if is_ptr_alias: schema, base = irtyputils.ptrcls_from_ptrref(expr_rptr.ptrref, schema=schema) self.set_attribute_value('expr', expr) required, card = expr.irast.cardinality.to_schema_value() spec_required = self.get_attribute_value('required') spec_card = self.get_attribute_value('cardinality') # If cardinality was unspecified and the computable is not # required, use the inferred cardinality. if spec_card is None and not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('cardinality', card) else: # Otherwise honor the spec, so no cardinality change, but check # that it's valid. if spec_card is None: # A computable link is marked explicitly as # "required", so we assume that omitted cardinality is # "single". Basically, to infer the cardinality both # cardinality-related qualifiers need to be omitted. spec_card = qltypes.SchemaCardinality.ONE if spec_required and not required: ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly an empty set returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx) if (spec_card is qltypes.SchemaCardinality.ONE and card != spec_card): ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly more than one element returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx) self.set_attribute_value('computable', True) return schema, target, base
def computable_ptr_set( rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, srcctx: Optional[parsing.ParserContext] = None, ctx: context.ContextLevel, ) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use the parent of derived ptrcls. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set(source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: source_rptrref = source_set.rptr.ptrref if source_rptrref.base_ptr is not None: source_rptrref = source_rptrref.base_ptr source_set.rptr = irast.Pointer( source=source_set.rptr.source, target=source_set, ptrref=source_rptrref, direction=source_set.rptr.direction, ) qlctx: Optional[context.ContextLevel] inner_source_path_id: Optional[irast.PathId] try: comp_info = ctx.source_map[ptrcls] qlexpr = comp_info.qlexpr assert isinstance(comp_info.context, context.ContextLevel) qlctx = comp_info.context inner_source_path_id = comp_info.path_id path_id_ns = comp_info.path_id_ns except KeyError: comp_expr = ptrcls.get_expr(ctx.env.schema) schema_qlexpr: Optional[qlast.Expr] = None if comp_expr is None and ctx.env.options.apply_query_rewrites: schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is not None: assert isinstance(ptrcls, s_pointers.Pointer) ptrcls_n = ptrcls.get_shortname(ctx.env.schema).name schema_qlexpr = qlast.BinOp( left=qlast.Path(steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name=ptrcls_n), direction=s_pointers.PointerDirection.Outbound, type=('property' if ptrcls.is_link_property( ctx.env.schema) else None)) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ) if schema_qlexpr is None: if comp_expr is None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise errors.InternalServerError( f'{ptrcls_sn!r} is not a computable pointer') comp_qlexpr = qlparser.parse(comp_expr.text) assert isinstance(comp_qlexpr, qlast.Expr), 'expected qlast.Expr' schema_qlexpr = comp_qlexpr # NOTE: Validation of the expression type is not the concern # of this function. For any non-object pointer target type, # the default expression must be assignment-cast into that # type. target_scls = ptrcls.get_target(ctx.env.schema) assert target_scls is not None if not target_scls.is_object_type(): schema_qlexpr = qlast.TypeCast( type=typegen.type_to_ql_typeref(target_scls, ctx=ctx), expr=schema_qlexpr, ) qlexpr = astutils.ensure_qlstmt(schema_qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None newctx: Callable[[], ContextManager[context.ContextLevel]] if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx(rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: src_path = rptr.target.path_id.src_path() assert src_path is not None source_path_id = src_path result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, ns=ctx.path_id_namespace, ctx=ctx, ) result_stype = ptrcls.get_target(ctx.env.schema) base_object = ctx.env.schema.get('std::BaseObject', type=s_types.Type) with newctx() as subctx: subctx.disable_shadowing.add(ptrcls) if result_stype != base_object: subctx.view_scls = result_stype subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) # type: ignore subctx.anchors[qlast.Source().name] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set # On a mutation, make the expr_exposed. This corresponds with # a similar check on is_mutation in _normalize_view_ptr_expr. if (source_scls.get_expr_type(ctx.env.schema) != s_types.ExprType.Select): subctx.expr_exposed = True if isinstance(qlexpr, qlast.Statement): subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=unnest_fence, iterator_target=True, ) comp_ir_set = ensure_set(dispatch.compile(qlexpr, ctx=subctx), ctx=subctx) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, context=srcctx, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_objtypes.ObjectType, *, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace]=None, is_insert: bool=False, is_update: bool=False, from_default: bool=False, view_rptr: Optional[context.ViewRPtr]=None, ctx: context.ContextLevel) -> s_pointers.Pointer: steps = shape_el.expr.steps is_linkprop = False is_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource: s_sources.Source = view_scls qlexpr: Optional[qlast.Expr] = None target_typexpr = None source: qlast.Base base_ptrcls_is_alias = False if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection): # Target type intersection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] if plen == 1: # regular shape lexpr = steps[0] assert isinstance(lexpr, qlast.Ptr) is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None or view_rptr.ptrcls is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) assert isinstance(view_rptr.ptrcls, s_links.Link) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection): # Source type intersection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type if not isinstance(ptype, qlast.TypeName): raise errors.QueryError( 'complex type expressions are not supported here', context=ptype.context, ) source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx) if not isinstance(source_spec, s_objtypes.ObjectType): raise errors.QueryError( f'expected object type, got ' f'{source_spec.get_verbosename(ctx.env.schema)}', context=ptype.context, ) ptrsource = source_spec is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') assert isinstance(lexpr, qlast.Ptr) ptrname = lexpr.ptr.name compexpr: Optional[qlast.Expr] = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError( "unexpected ':'", context=steps[-1].context) ptrcls: Optional[s_pointers.Pointer] if compexpr is None: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_ptr( ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_is_singleton = False if base_cardinality is not None and base_cardinality.is_known(): base_is_singleton = base_cardinality.is_single() if ( shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None or (ctx.implicit_limit and not base_is_singleton) ): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIntersection(type=target_typexpr), ]) qlexpr = astutils.ensure_qlstmt(qlexpr) assert isinstance(qlexpr, qlast.SelectQuery) qlexpr.where = shape_el.where qlexpr.orderby = shape_el.orderby if shape_el.offset or shape_el.limit: qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True) qlexpr.offset = shape_el.offset qlexpr.limit = shape_el.limit if ( (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and not qlexpr.limit and ctx.implicit_limit and not base_is_singleton ): qlexpr.limit = qlast.IntegerConstant( value=str(ctx.implicit_limit), ) if target_typexpr is not None: assert isinstance(target_typexpr, qlast.TypeName) intersector_type = schemactx.get_schema_type( target_typexpr.maintype, ctx=ctx) int_result = schemactx.apply_intersection( ptrcls.get_target(ctx.env.schema), # type: ignore intersector_type, ctx=ctx, ) ptr_target = int_result.stype else: _ptr_target = ptrcls.get_target(ctx.env.schema) assert _ptr_target ptr_target = _ptr_target ptr_cardinality = base_cardinality if ptr_cardinality is None or not ptr_cardinality.is_known(): # We do not know the parent's pointer cardinality yet. ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) ctx.env.pointer_specified_info[ptrcls] = ( shape_el.cardinality, shape_el.required, shape_el.context) implicit_tid = has_implicit_type_computables( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id( path_id, ptrcls=base_ptrcls, ns=ctx.path_id_namespace, ctx=ctx) ctx.path_scope.attach_path(sub_path_id, context=shape_el.context) if not isinstance(ptr_target, s_objtypes.ObjectType): raise errors.QueryError( f'shapes cannot be applied to ' f'{ptr_target.get_verbosename(ctx.env.schema)}', context=shape_el.context, ) 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.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, parser_context=shape_el.context, ctx=ctx) else: ptr_target = _process_view( stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, parser_context=shape_el.context, ctx=ctx) else: base_ptrcls = ptrcls = None if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=lexpr, ctx=ctx) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) ptr_name = sn.QualName( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: ptr_name = sn.QualName( module='__', name=ptrname, ) try: ptrcls = setgen.resolve_ptr( ptrsource, ptrname, track_ref=False, ctx=ctx, ) base_ptrcls = ptrcls.get_bases( ctx.env.schema).first(ctx.env.schema) except errors.InvalidReferenceError: # This is a NEW computable pointer, it's fine. pass qlexpr = astutils.ensure_qlstmt(compexpr) if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt) and ctx.implicit_limit and isinstance(qlexpr, qlast.OffsetLimitMixin) and not qlexpr.limit): qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit)) 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( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update, ) shape_expr_ctx.defining_view = view_scls shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls.get_bases(ctx.env.schema).first(ctx.env.schema), path_id=path_id, ctx=shape_expr_ctx) prefix_rptrref = path_id.rptr() if prefix_rptrref is not None: # Source path seems to contain multiple steps, # so set up a rptr for abbreviated link property # paths. src_path_id = path_id.src_path() assert src_path_id is not None ctx.env.schema, src_t = irtyputils.ir_typeref_to_type( shape_expr_ctx.env.schema, src_path_id.target, ) prefix_rptr = irast.Pointer( source=setgen.class_set( src_t, path_id=src_path_id, ctx=shape_expr_ctx, ), target=shape_expr_ctx.partial_path_prefix, ptrref=prefix_rptrref, direction=s_pointers.PointerDirection.Outbound, ) shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata( iterator_target=True, ) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) if ( shape_el.operation.op is qlast.ShapeOp.APPEND or shape_el.operation.op is qlast.ShapeOp.SUBTRACT ): if not is_update: op = ( '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND else '-=' ) raise errors.EdgeQLSyntaxError( f"unexpected '{op}'", context=shape_el.operation.context, ) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls base_ptrcls_is_alias = shape_expr_ctx.view_rptr.ptrcls_is_alias if ptrcls is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True) ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) if ( isinstance(ptr_target, s_types.Collection) and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None) ): # Record references to implicitly defined collection types, # so that the alias delta machinery can pick them up. ctx.env.created_schema_objects.add(ptr_target) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) assert base_target is not None if ptr_target.assignment_castable_to( base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not ( base_target.is_object_type() or s_types.is_type_compatible( base_target, ptr_target, schema=ctx.env.schema ) ): qlexpr = astutils.ensure_qlstmt(qlast.TypeCast( type=typegen.type_to_ql_typeref(base_target, ctx=ctx), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] ercls: Type[errors.EdgeDBError] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})' ) if qlexpr is not None or ptrcls is None: src_scls: s_sources.Source if is_linkprop: # Proper checking was done when is_linkprop is defined. assert view_rptr is not None assert isinstance(view_rptr.ptrcls, s_links.Link) src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object( sn.QualName('std', 'link'), expr=None) else: base = ctx.env.get_track_schema_object( sn.QualName('std', 'property'), expr=None) if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[str(src_scls.get_name(ctx.env.schema))], ctx=ctx, ) existing = ctx.env.schema.get( derived_name, default=None, type=s_pointers.Pointer) if existing is not None: existing_target = existing.get_target(ctx.env.schema) assert existing_target is not None if ctx.recompiling_schema_alias: ptr_cardinality = existing.get_cardinality(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to( existing_target, ctx.env.schema): ctx.env.schema = existing.set_target( ctx.env.schema, ptr_target) ptrcls = existing else: vnp = existing.get_verbosename( ctx.env.schema, with_parent=True) t1_vn = existing_target.get_verbosename(ctx.env.schema) t2_vn = ptr_target.get_verbosename(ctx.env.schema) if compexpr is not None: source_context = compexpr.context else: source_context = shape_el.expr.steps[-1].context raise errors.SchemaError( f'cannot redefine {vnp} as {t2_vn}', details=f'{vnp} is defined as {t1_vn}', context=source_context, ) else: ptrcls = schemactx.derive_ptr( derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) assert ptrcls is not None if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'path_id_name', path_id_name ) if qlexpr is not None: ctx.source_map[ptrcls] = irast.ComputableInfo( qlexpr=qlexpr, context=ctx, path_id=path_id, path_id_ns=path_id_namespace, shape_op=shape_el.operation.op, ) if compexpr is not None or is_polymorphic: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'computable', True, ) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'owned', True, ) if ptr_cardinality is not None: ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', ptr_cardinality) else: if qlexpr is None and ptrcls is not base_ptrcls: ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls) base_cardinality = None base_required = False if base_ptrcls is not None and not base_ptrcls_is_alias: base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx) base_required = base_ptrcls.get_required(ctx.env.schema) if base_cardinality is None or not base_cardinality.is_known(): specified_cardinality = shape_el.cardinality specified_required = shape_el.required else: specified_cardinality = base_cardinality specified_required = base_required if (shape_el.cardinality is not None and base_ptrcls is not None and shape_el.cardinality != base_cardinality): base_src = base_ptrcls.get_source(ctx.env.schema) assert base_src is not None base_src_name = base_src.get_verbosename(ctx.env.schema) raise errors.SchemaError( f'cannot redefine the cardinality of ' f'{ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is defined as {base_cardinality.as_ptr_qual()!r} ' f'in the base {base_src_name}', context=compexpr and compexpr.context, ) # The required flag may be inherited from the base specified_required = shape_el.required or base_required ctx.env.pointer_specified_info[ptrcls] = ( specified_cardinality, specified_required, shape_el.context) ctx.env.schema = ptrcls.set_field_value( ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown) if ( ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None and not from_default and not ctx.env.options.allow_writing_protected_pointers ): ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) if is_update and ptrcls.get_readonly(ctx.env.schema): raise errors.QueryError( f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: ' f'it is declared as read-only', context=compexpr and compexpr.context, ) return ptrcls
def _get_shape_configuration( ir_set: irast.Set, *, rptr: Optional[irast.Pointer]=None, parent_view_type: Optional[s_types.ExprType]=None, ctx: context.ContextLevel ) -> List[Tuple[irast.Set, s_pointers.Pointer, qlast.ShapeOp]]: """Return a list of (source_set, ptrcls) pairs as a shape for a given set. """ stype = setgen.get_set_type(ir_set, ctx=ctx) sources: List[ Union[s_types.Type, s_pointers.PointerLike]] = [] link_view = False is_objtype = ir_set.path_id.is_objtype_path() if rptr is None: rptr = ir_set.rptr if rptr is not None: rptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) else: rptrcls = None link_view = ( rptrcls is not None and not rptrcls.is_link_property(ctx.env.schema) and _link_has_shape(rptrcls, ctx=ctx) ) if is_objtype or not link_view: sources.append(stype) if link_view: sources.append(rptrcls) shape_ptrs = [] for source in sources: for ptr, shape_op in ctx.env.view_shapes[source]: if (ptr.is_link_property(ctx.env.schema) and ir_set.path_id != rptr.target.path_id): path_tip = rptr.target else: path_tip = ir_set shape_ptrs.append((path_tip, ptr, shape_op)) if is_objtype: assert isinstance(stype, s_objtypes.ObjectType) view_type = stype.get_expr_type(ctx.env.schema) is_mutation = view_type in (s_types.ExprType.Insert, s_types.ExprType.Update) is_parent_update = parent_view_type is s_types.ExprType.Update implicit_id = ( # shape is not specified at all not shape_ptrs # implicit ids are always wanted or (ctx.implicit_id_in_shapes and not is_mutation) # we are inside an UPDATE shape and this is # an explicit expression (link target update) or (is_parent_update and ir_set.expr is not None) ) if implicit_id: # We want the id in this shape and it's not already there, # so insert it in the first position. pointers = stype.get_pointers(ctx.env.schema).objects( ctx.env.schema) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} for ptr in pointers: if ptr.is_id_pointer(ctx.env.schema): if ptr not in view_shape_ptrs: shape_metadata = ctx.env.view_shapes_metadata[stype] view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_metadata.has_implicit_id = True shape_ptrs.insert( 0, (ir_set, ptr, qlast.ShapeOp.ASSIGN)) break is_mutation = parent_view_type in { s_types.ExprType.Insert, s_types.ExprType.Update } implicit_tid = ( stype is not None and has_implicit_tid(stype, is_mutation=is_mutation, ctx=ctx) ) if implicit_tid: assert isinstance(stype, s_objtypes.ObjectType) try: ptr = setgen.resolve_ptr(stype, '__tid__', ctx=ctx) except errors.InvalidReferenceError: ql = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef(name='__tid__'), direction=s_pointers.PointerDirection.Outbound, )], ), compexpr=qlast.Path( steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name='__type__'), direction=s_pointers.PointerDirection.Outbound, ), qlast.Ptr( ptr=qlast.ObjectRef(name='id'), direction=s_pointers.PointerDirection.Outbound, ) ] ) ) with ctx.newscope(fenced=True) as scopectx: scopectx.anchors = scopectx.anchors.copy() scopectx.anchors[qlast.Source().name] = ir_set ptr = _normalize_view_ptr_expr( ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] view_shape_ptrs = {p for p, _ in view_shape} if ptr not in view_shape_ptrs: view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN)) shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN)) return shape_ptrs
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils from edb.schema import objtypes as s_objtypes # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) assert parent_ctx is not None source_name = parent_ctx.op.classname source = schema.get(source_name, type=s_objtypes.ObjectType) expression = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=frozenset([source]), ), ) assert isinstance(expression.irast, irast.Statement) base = None target = expression.irast.stype result_expr = expression.irast.expr.expr if (isinstance(result_expr, irast.SelectStmt) and result_expr.result.rptr is not None): expr_rptr = result_expr.result.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound) if is_ptr_alias: new_schema, base = irtyputils.ptrcls_from_ptrref( expr_rptr.ptrref, schema=schema) # Only pointers coming from the same source as the # alias should be "inherited" (in order to preserve # link props). Random paths coming from other sources # get treated same as any other arbitrary expression # in a computable. if base.get_source(new_schema) != source: base = None else: schema = new_schema self.set_attribute_value('expr', expression) required, card = expression.irast.cardinality.to_schema_value() spec_required = self.get_attribute_value('required') spec_card = self.get_attribute_value('cardinality') if spec_required and not required: ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly an empty set returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx) if (spec_card in {None, qltypes.SchemaCardinality.ONE} and card is not qltypes.SchemaCardinality.ONE): ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly more than one element returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx) if spec_card is None: self.set_attribute_value('cardinality', card) if not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('computable', True) return schema, target, base
def _normalize_view_ptr_expr( shape_el: qlast.ShapeElement, view_scls: s_nodes.Node, *, path_id: irast.PathId, path_id_namespace: typing.Optional[irast.WeakNamespace] = None, 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_polymorphic = False is_mutation = is_insert or is_update # Pointers may be qualified by the explicit source # class, which is equivalent to Expr[IS Type]. plen = len(steps) ptrsource = view_scls qlexpr = None if plen >= 2 and isinstance(steps[-1], qlast.TypeIndirection): # Target type indirection: foo: Type target_typexpr = steps[-1].type plen -= 1 steps = steps[:-1] else: target_typexpr = None if plen == 1: # regular shape lexpr = steps[0] is_linkprop = lexpr.type == 'property' if is_linkprop: if view_rptr is None: raise errors.QueryError( 'invalid reference to link property ' 'in top level shape', context=lexpr.context) ptrsource = view_rptr.ptrcls source = qlast.Source() elif plen == 2 and isinstance(steps[0], qlast.TypeIndirection): # Source type indirection: [IS Type].foo source = qlast.Path(steps=[ qlast.Source(), steps[0], ]) lexpr = steps[1] ptype = steps[0].type ptrsource = schemactx.get_schema_type(ptype.maintype, ctx=ctx) is_polymorphic = True else: # pragma: no cover raise RuntimeError( f'unexpected path length in view shape: {len(steps)}') ptrname = lexpr.ptr.name compexpr = shape_el.compexpr if compexpr is None and is_insert and shape_el.elements: # Short shape form in INSERT, e.g # INSERT Foo { bar: Spam { name := 'name' }} # is prohibited. raise errors.EdgeQLSyntaxError("unexpected ':'", context=steps[-1].context) if compexpr is None: ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) if is_polymorphic: ptrcls = schemactx.derive_view(ptrcls, view_scls, is_insert=is_insert, is_update=is_update, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema) base_ptr_is_computable = base_ptrcls in ctx.source_map ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) if (shape_el.where or shape_el.orderby or shape_el.offset or shape_el.limit or base_ptr_is_computable or is_polymorphic or target_typexpr is not None): if target_typexpr is None: qlexpr = qlast.Path(steps=[source, lexpr]) else: qlexpr = qlast.Path(steps=[ source, lexpr, qlast.TypeIndirection(type=target_typexpr), ]) 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 if target_typexpr is not None: ptr_target = schemactx.get_schema_type(target_typexpr.maintype, ctx=ctx) else: ptr_target = ptrcls.get_target(ctx.env.schema) if base_ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, from_parent=True, source_ctx=shape_el.context, ctx=ctx) else: ptr_cardinality = base_ptrcls.get_cardinality(ctx.env.schema) implicit_tid = has_implicit_tid( ptr_target, is_mutation=is_mutation, ctx=ctx, ) if shape_el.elements or implicit_tid: sub_view_rptr = context.ViewRPtr( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, is_insert=is_insert, is_update=is_update) sub_path_id = pathctx.extend_path_id(path_id, ptrcls=base_ptrcls, target=ptrcls.get_target( ctx.env.schema), ns=ctx.path_id_namespace, ctx=ctx) 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.QueryError( 'only references to link properties are allowed ' 'in nested UPDATE shapes', context=subel.context) ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, is_update=True, ctx=ctx) else: ptr_target = _process_view(stype=ptr_target, path_id=sub_path_id, path_id_namespace=path_id_namespace, view_rptr=sub_view_rptr, elements=shape_el.elements, ctx=ctx) else: if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) base_ptrcls = ptrcls.get_bases(ctx.env.schema).first( ctx.env.schema) ptr_name = sn.Name( module='__', name=ptrcls.get_shortname(ctx.env.schema).name, ) else: # Otherwise, assume no pointer inheritance. # Every computable is a new pointer derived from # std::link or std::property. There is one exception: # pointer aliases (Foo {some := Foo.other}), where `foo` # gets derived from `Foo.other`. This logic is applied # in compile_query_subject() by populating the base_ptrcls. base_ptrcls = ptrcls = None ptr_name = sn.Name( module='__', name=ptrname, ) 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( ptrsource if is_linkprop else view_scls, ptrcls=ptrcls, ptrcls_name=ptr_name, ptrcls_is_linkprop=is_linkprop, is_insert=is_insert, is_update=is_update) shape_expr_ctx.path_scope.unnest_fence = True shape_expr_ctx.partial_path_prefix = setgen.class_set( view_scls, path_id=path_id, ctx=shape_expr_ctx) if is_mutation and ptrcls is not None: shape_expr_ctx.expr_exposed = True shape_expr_ctx.empty_result_type_hint = \ ptrcls.get_target(ctx.env.schema) irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls ptr_cardinality = None ptr_target = inference.infer_type(irexpr, ctx.env) anytype = ptr_target.find_any(ctx.env.schema) if anytype is not None: raise errors.QueryError( 'expression returns value of indeterminate type', context=ctx.env.type_origins.get(anytype), ) # Validate that the insert/update expression is # of the correct class. if is_mutation and ptrcls is not None: base_target = ptrcls.get_target(ctx.env.schema) if ptr_target.assignment_castable_to(base_target, schema=ctx.env.schema): # Force assignment casts if the target type is not a # subclass of the base type and the cast is not to an # object type. if not (base_target.is_object_type() or ptr_target.issubclass(ctx.env.schema, base_target)): qlexpr = astutils.ensure_qlstmt( qlast.TypeCast( type=astutils.type_to_ql_typeref( base_target, schema=ctx.env.schema), expr=compexpr, )) ptr_target = base_target else: expected = [ repr(str(base_target.get_displayname(ctx.env.schema))) ] if ptrcls.is_property(ctx.env.schema): ercls = errors.InvalidPropertyTargetError else: ercls = errors.InvalidLinkTargetError ptr_vn = ptrcls.get_verbosename(ctx.env.schema, with_parent=True) raise ercls( f'invalid target for {ptr_vn}: ' f'{str(ptr_target.get_displayname(ctx.env.schema))!r} ' f'(expecting {" or ".join(expected)})') if qlexpr is not None or ptrcls is None: if is_linkprop: src_scls = view_rptr.ptrcls else: src_scls = view_scls if ptr_target.is_object_type(): base = ctx.env.get_track_schema_object('std::link') else: base = ctx.env.get_track_schema_object('std::property') if base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( base_ptrcls, derived_name_base=ptr_name, derived_name_quals=[src_scls.get_name(ctx.env.schema)], ctx=ctx) existing = ctx.env.schema.get(derived_name, None) if existing is not None: existing_target = existing.get_target(ctx.env.schema) if ptr_target == existing_target: ptrcls = existing elif ptr_target.implicitly_castable_to(existing_target, ctx.env.schema): ctx.env.schema = existing.set_target(ctx.env.schema, ptr_target) ptrcls = existing else: target_rptr_set = (ptr_target.get_rptr(ctx.env.schema) is not None) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', None, ) ctx.env.schema = existing.delete(ctx.env.schema) ptrcls = schemactx.derive_view(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, inheritance_merge=False, ctx=ctx) if target_rptr_set: ctx.env.schema = ptr_target.set_field_value( ctx.env.schema, 'rptr', ptrcls, ) else: ptrcls = schemactx.derive_view(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, ctx=ctx) elif ptrcls.get_target(ctx.env.schema) != ptr_target: ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target) if qlexpr is None: # This is not a computable, just a pointer # to a nested shape. Have it reuse the original # pointer name so that in `Foo.ptr.name` and # `Foo { ptr: {name}}` are the same path. path_id_name = base_ptrcls.get_name(ctx.env.schema) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'path_id_name', path_id_name) if qlexpr is not None: ctx.source_map[ptrcls] = (qlexpr, ctx, path_id, path_id_namespace) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'computable', True) if not is_mutation: if ptr_cardinality is None: if ptrcls not in ctx.pending_cardinality: if qlexpr is not None: from_parent = False elif ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) from_parent = True else: from_parent = False stmtctx.pend_pointer_cardinality_inference( ptrcls=ptrcls, specified_card=shape_el.cardinality, from_parent=from_parent, source_ctx=shape_el.context, ctx=ctx) ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', None) else: ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality', ptr_cardinality) if ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) if is_polymorphic: msg = (f'cannot access {ptrcls_sn.name} on a polymorphic ' f'shape element') else: msg = f'cannot assign to {ptrcls_sn.name}' raise errors.QueryError(msg, context=shape_el.context) return ptrcls
def reduce_DUNDERSOURCE(self, *kids): self.val = qlast.Path(steps=[qlast.Source()])
def _get_shape_configuration( ir_set: irast.Set, *, rptr: typing.Optional[irast.Pointer]=None, parent_view_type: typing.Optional[s_types.ViewType]=None, ctx: context.ContextLevel) \ -> typing.List[typing.Tuple[irast.Set, s_pointers.Pointer]]: """Return a list of (source_set, ptrcls) pairs as a shape for a given set. """ stype = setgen.get_set_type(ir_set, ctx=ctx) sources = [] link_view = False is_objtype = ir_set.path_id.is_objtype_path() if rptr is None: rptr = ir_set.rptr if rptr is not None: rptrcls = irtyputils.ptrcls_from_ptrref(rptr.ptrref, schema=ctx.env.schema) else: rptrcls = None link_view = (rptrcls is not None and not rptrcls.is_link_property(ctx.env.schema) and _link_has_shape(rptrcls, ctx=ctx)) if is_objtype or not link_view: sources.append(stype) if link_view: sources.append(rptrcls) shape_ptrs = [] id_present_in_shape = False for source in sources: for ptr in ctx.env.view_shapes[source]: if (ptr.is_link_property(ctx.env.schema) and ir_set.path_id != rptr.target.path_id): path_tip = rptr.target else: path_tip = ir_set shape_ptrs.append((path_tip, ptr)) if source is stype and ptr.is_id_pointer(ctx.env.schema): id_present_in_shape = True if is_objtype and not id_present_in_shape: view_type = stype.get_view_type(ctx.env.schema) is_mutation = view_type in (s_types.ViewType.Insert, s_types.ViewType.Update) is_parent_update = parent_view_type is s_types.ViewType.Update implicit_id = ( # shape is not specified at all not shape_ptrs # implicit ids are always wanted or (ctx.implicit_id_in_shapes and not is_mutation) # we are inside an UPDATE shape and this is # an explicit expression (link target update) or (is_parent_update and ir_set.expr is not None)) if implicit_id: # We want the id in this shape and it's not already there, # so insert it in the first position. pointers = stype.get_pointers(ctx.env.schema).objects( ctx.env.schema) for ptr in pointers: if ptr.is_id_pointer(ctx.env.schema): view_shape = ctx.env.view_shapes[stype] if ptr not in view_shape: shape_metadata = ctx.env.view_shapes_metadata[stype] view_shape.insert(0, ptr) shape_metadata.has_implicit_id = True shape_ptrs.insert(0, (ir_set, ptr)) break if (ir_set.typeref is not None and irtyputils.is_object(ir_set.typeref) and parent_view_type is not s_types.ViewType.Insert and parent_view_type is not s_types.ViewType.Update and ctx.implicit_tid_in_shapes): ql = qlast.ShapeElement( expr=qlast.Path(steps=[ qlast.Ptr( ptr=qlast.ObjectRef(name='__tid__'), direction=s_pointers.PointerDirection.Outbound, ) ], ), compexpr=qlast.Path(steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name='__type__'), direction=s_pointers.PointerDirection.Outbound, ), qlast.Ptr( ptr=qlast.ObjectRef(name='id'), direction=s_pointers.PointerDirection.Outbound, ) ])) with ctx.newscope(fenced=True) as scopectx: scopectx.anchors = scopectx.anchors.copy() scopectx.anchors[qlast.Source] = ir_set ptr = _normalize_view_ptr_expr(ql, stype, path_id=ir_set.path_id, ctx=scopectx) view_shape = ctx.env.view_shapes[stype] if ptr not in view_shape: view_shape.insert(0, ptr) shape_ptrs.insert(0, (ir_set, ptr)) return shape_ptrs
def computable_ptr_set( rptr: irast.Pointer, *, unnest_fence: bool=False, hoist_iterators: bool=False, same_computable_scope: bool=False, ctx: context.ContextLevel, ) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use the parent of derived ptrcls. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set( source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: source_set.rptr = irast.Pointer( source=source_set.rptr.source, target=source_set, ptrref=source_set.rptr.ptrref.base_ptr, direction=source_set.rptr.direction, ) qlctx: Optional[context.ContextLevel] inner_source_path_id: Optional[irast.PathId] try: comp_info = ctx.source_map[ptrcls] qlexpr = comp_info.qlexpr qlctx = comp_info.context inner_source_path_id = comp_info.path_id path_id_ns = comp_info.path_id_ns except KeyError: comp_expr = ptrcls.get_expr(ctx.env.schema) if comp_expr is None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise ValueError(f'{ptrcls_sn!r} is not a computable pointer') qlexpr = qlparser.parse(comp_expr.text) # NOTE: Validation of the expression type is not the concern # of this function. For any non-object pointer target type, # the default expression must be assignment-cast into that # type. target_scls = ptrcls.get_target(ctx.env.schema) assert target_scls is not None if not target_scls.is_object_type(): qlexpr = qlast.TypeCast( type=astutils.type_to_ql_typeref( target_scls, schema=ctx.env.schema), expr=qlexpr, ) qlexpr = astutils.ensure_qlstmt(qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None newctx: Callable[[], ContextManager[context.ContextLevel]] if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx( rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: src_path = rptr.target.path_id.src_path() assert src_path is not None source_path_id = src_path result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, ns=ctx.path_id_namespace, ctx=ctx) result_stype = ptrcls.get_target(ctx.env.schema) with newctx() as subctx: subctx.view_scls = result_stype assert isinstance(source_scls, s_sources.Source) subctx.view_rptr = context.ViewRPtr( source_scls, ptrcls=ptrcls, rptr=rptr) subctx.anchors[qlast.Source().name] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set if isinstance(qlexpr, qlast.Statement): subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=unnest_fence, iterator_target=True, ) comp_ir_set = ensure_set( dispatch.compile(qlexpr, ctx=subctx), ctx=subctx) comp_ir_set_copy = new_set_from_set(comp_ir_set, ctx=ctx) pending_cardinality = ctx.pending_cardinality.get(ptrcls) if pending_cardinality is not None: stmtctx.get_pointer_cardinality_later( ptrcls=ptrcls, irexpr=comp_ir_set_copy, specified_card=pending_cardinality.specified_cardinality, is_mut_assignment=pending_cardinality.is_mut_assignment, shape_op=pending_cardinality.shape_op, source_ctx=pending_cardinality.source_ctx, ctx=ctx, ) if ( pending_cardinality is None or pending_cardinality.shape_op is not qlast.ShapeOp.SUBTRACT ): # When doing subtraction in shapes, the cardinality of the # expression being subtracted does not matter. stmtctx.enforce_pointer_cardinality( ptrcls, comp_ir_set_copy, singletons={source_path_id}, ctx=ctx, ) comp_ir_set = new_set_from_set( comp_ir_set, path_id=result_path_id, rptr=rptr, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set