def declare_view( expr: qlast.Base, alias: str, *, fully_detached: bool=False, temporary_scope: bool=True, ctx: context.ContextLevel) -> irast.Set: with ctx.newscope(temporary=temporary_scope, fenced=True) as subctx: if not fully_detached: cached_view_set = ctx.expr_view_cache.get((expr, alias)) # Detach the view namespace and record the prefix # in the parent statement's fence node. view_path_id_ns = irast.WeakNamespace(ctx.aliases.get('ns')) subctx.path_id_namespace |= {view_path_id_ns} ctx.path_scope.add_namespaces((view_path_id_ns,)) else: cached_view_set = None if ctx.stmt is not None: subctx.stmt = ctx.stmt.parent_stmt if cached_view_set is not None: subctx.view_scls = setgen.get_set_type(cached_view_set, ctx=ctx) view_name = subctx.view_scls.get_name(ctx.env.schema) else: if isinstance(alias, s_name.SchemaName): basename = alias else: basename = s_name.SchemaName(module='__derived__', name=alias) view_name = s_name.SchemaName( module=ctx.derived_target_module or '__derived__', name=s_name.get_specialized_name( basename, ctx.aliases.get('w') ) ) subctx.toplevel_result_view_name = view_name view_set = dispatch.compile(astutils.ensure_qlstmt(expr), ctx=subctx) if not fully_detached: # The view path id _itself_ should not be in the nested namespace. # The fully_detached case should be handled by the caller. view_set.path_id = view_set.path_id.replace_namespace( ctx.path_id_namespace) ctx.aliased_views[alias] = setgen.get_set_type(view_set, ctx=ctx) ctx.path_scope_map[view_set] = subctx.path_scope ctx.expr_view_cache[expr, alias] = view_set return view_set
async def read_objtypes(self, schema, only_modules, exclude_modules): objtype_list = await datasources.schema.objtypes.fetch( self.connection, modules=only_modules, exclude_modules=exclude_modules) objtype_list = {sn.Name(row['name']): row for row in objtype_list} basemap = {} for name, row in objtype_list.items(): objtype = { 'id': row['id'], 'name': name, 'is_abstract': row['is_abstract'], 'is_final': row['is_final'], 'view_type': (s_types.ViewType(row['view_type']) if row['view_type'] else None), 'expr': (s_expr.Expression(**row['expr']) if row['expr'] else None) } basemap[name] = row['bases'] or [] schema, objtype = s_objtypes.ObjectType.create_in_schema( schema, id=objtype['id'], name=name, is_abstract=objtype['is_abstract'], is_final=objtype['is_final'], view_type=objtype['view_type'], expr=objtype['expr']) for objtype in schema.get_objects(type=s_objtypes.BaseObjectType): try: bases = basemap[objtype.get_name(schema)] except KeyError: pass else: schema = objtype.set_field_value( schema, 'bases', [schema.get(b) for b in bases]) derived = await datasources.schema.objtypes.fetch_derived( self.connection) for row in derived: attrs = dict(row) attrs['name'] = sn.SchemaName(attrs['name']) attrs['bases'] = [schema.get(b) for b in attrs['bases']] attrs['view_type'] = (s_types.ViewType(attrs['view_type']) if attrs['view_type'] else None) attrs['expr'] = (s_expr.Expression(**row['expr']) if row['expr'] else None) attrs['is_derived'] = True schema, objtype = s_objtypes.ObjectType.create_in_schema( schema, **attrs) return schema
def derive_view_name( stype: s_obj.Object, derived_name_quals: typing.Optional[typing.Sequence[str]]=(), derived_name_base: typing.Optional[str]=None, *, ctx: context.ContextLevel) -> sn.Name: if not derived_name_quals: derived_name_quals = (ctx.aliases.get('view'),) if not derived_name_base: derived_name_base = stype.get_shortname(ctx.env.schema) if ctx.derived_target_module: derived_name_module = ctx.derived_target_module else: derived_name_module = '__derived__' derived_sname = sn.get_specialized_name( derived_name_base, *derived_name_quals) return sn.SchemaName(module=derived_name_module, name=derived_sname)
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) stype = view_scls.peel_view(ctx.env.schema) ptrsource = stype 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) if view_rptr.ptrcls is None: derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) ptrsource = stype = 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 ptrcls_is_derived = False 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: base_ptrcls = ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) base_ptr_is_computable = ptrcls in ctx.source_map ptr_name = ptrcls.get_shortname(ctx.env.schema) 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 ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None else: ptr_cardinality = ptrcls.get_cardinality(ctx.env.schema) if shape_el.elements: 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=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) 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 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 else: if (is_mutation and ptrname not in ctx.special_computables_in_mutation_shape): # If this is a mutation, the pointer must exist. base_ptrcls = ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx) ptr_name = ptrcls.get_shortname(ctx.env.schema) 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_module = (ctx.derived_target_module or stype.get_name(ctx.env.schema).module) ptr_name = sn.SchemaName(module=ptr_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 if ptrcls is None: ptrcls = shape_expr_ctx.view_rptr.ptrcls derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls if derived_ptrcls is not None: ptrcls_is_derived = True ptrcls = derived_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), ) if (is_mutation and base_ptrcls is not None and not ptr_target.assignment_castable_to( base_ptrcls.get_target(ctx.env.schema), schema=ctx.env.schema)): # Validate that the insert/update expression is # of the correct class. ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) expected = [ repr( str( base_ptrcls.get_target(ctx.env.schema).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 ptr_target is not ptrcls.get_target(ctx.env.schema)): if not ptrcls_is_derived: if is_linkprop: rptrcls = view_rptr.derived_ptrcls if rptrcls is None: rptrcls = derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) src_scls = rptrcls else: src_scls = view_scls 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 = ptrcls.get_name(ctx.env.schema) else: path_id_name = None if ptr_target.is_object_type(): base = ctx.env.schema.get('std::link') else: base = ctx.env.schema.get('std::property') if ptrcls is not None: derive_from = ptrcls elif base_ptrcls is not None: derive_from = base_ptrcls else: derive_from = base derived_name = schemactx.derive_view_name( ptrcls, derived_name_base=ptr_name, derived_name_quals=[view_scls.get_name(ctx.env.schema)], ctx=ctx) existing = ctx.env.schema.get(derived_name, None) if existing is not None: ptrcls = existing else: attrs = dict(path_id_name=path_id_name, bases=[base]) ptrcls = schemactx.derive_view(derive_from, src_scls, ptr_target, is_insert=is_insert, is_update=is_update, derived_name=derived_name, merge_bases=[derive_from], attrs=attrs, ctx=ctx) 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 compexpr is not None: from_parent = False elif ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) from_parent = True 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 declare_view( expr: qlast.Expr, alias: str, *, fully_detached: bool=False, must_be_used: bool=False, path_id_namespace: Optional[FrozenSet[str]]=None, ctx: context.ContextLevel, ) -> irast.Set: pinned_pid_ns = path_id_namespace with ctx.newscope(temporary=True, fenced=True) as subctx: if path_id_namespace is not None: subctx.path_id_namespace = path_id_namespace if not fully_detached: cached_view_set = ctx.expr_view_cache.get((expr, alias)) # Detach the view namespace and record the prefix # in the parent statement's fence node. view_path_id_ns = irast.WeakNamespace(ctx.aliases.get('ns')) subctx.path_id_namespace |= {view_path_id_ns} ctx.path_scope.add_namespaces({view_path_id_ns}) else: cached_view_set = None if ctx.stmt is not None: subctx.stmt = ctx.stmt.parent_stmt if cached_view_set is not None: subctx.view_scls = setgen.get_set_type(cached_view_set, ctx=ctx) view_name = s_name.SchemaName( subctx.view_scls.get_name(ctx.env.schema)) else: if isinstance(alias, s_name.SchemaName): basename = alias else: basename = s_name.SchemaName(module='__derived__', name=alias) view_name = s_name.SchemaName( module=ctx.derived_target_module or '__derived__', name=s_name.get_specialized_name( basename, ctx.aliases.get('w') ) ) subctx.toplevel_result_view_name = view_name view_set = dispatch.compile(astutils.ensure_qlstmt(expr), ctx=subctx) assert isinstance(view_set, irast.Set) ctx.path_scope_map[view_set] = context.ScopeInfo( path_scope=subctx.path_scope, pinned_path_id_ns=pinned_pid_ns, tentative_work=[ cb for cb in subctx.tentative_work if cb not in ctx.tentative_work ], ) if not fully_detached: # The view path id _itself_ should not be in the nested namespace. # The fully_detached case should be handled by the caller. if path_id_namespace is None: path_id_namespace = ctx.path_id_namespace view_set.path_id = view_set.path_id.replace_namespace( path_id_namespace) view_type = setgen.get_set_type(view_set, ctx=ctx) ctx.aliased_views[alias] = view_type ctx.expr_view_cache[expr, alias] = view_set if must_be_used: ctx.must_use_views[view_type] = (alias, expr.context) return view_set