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 = collections.OrderedDict( (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.ExpressionText(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['is_derived'] = True schema, objtype = s_objtypes.ObjectType.create_in_schema( schema, **attrs) return schema
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. add_ns = (irast.WeakNamespace(ctx.aliases.get('ns')), ) subctx.path_id_namespace = subctx.path_id_namespace + add_ns ctx.path_scope.namespaces.add(subctx.path_id_namespace[-1]) 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 = cached_view_set.scls view_name = cached_view_set.scls.name else: if isinstance(alias, s_name.SchemaName): basename = alias else: basename = s_name.SchemaName(module='__view__', name=alias) view_name = s_name.SchemaName( module=ctx.derived_target_module or '_', name=s_obj.NamedObject.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) # The view path id _itself_ should not be in the nested namespace. view_set.path_id = view_set.path_id.replace_namespace( ctx.path_id_namespace) ctx.aliased_views[alias] = view_set.scls ctx.path_scope_map[view_set] = subctx.path_scope ctx.expr_view_cache[expr, alias] = view_set return view_set
def derive_view_name( scls: 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 = scls.shortname if ctx.derived_target_module: derived_name_module = ctx.derived_target_module else: derived_name_module = '__view__' derived_sname = scls.get_specialized_name( derived_name_base, *derived_name_quals) return sn.SchemaName(module=derived_name_module, name=derived_sname)
def derive_view(scls: s_obj.Object, source: typing.Optional[s_nodes.Node] = None, target: typing.Optional[s_nodes.Node] = None, *qualifiers, derived_name: typing.Optional[sn.SchemaName] = None, derived_name_quals: typing.Optional[typing.Sequence[str]] = (), is_insert: bool = False, is_update: bool = False, add_to_schema: bool = True, ctx: context.ContextLevel) -> s_obj.Object: if source is None: source = scls if derived_name is None: if not derived_name_quals: derived_name_quals = (ctx.aliases.get('view'), ) if ctx.derived_target_module: derived_sname = scls.get_specialized_name(scls.shortname, *derived_name_quals) derived_name = sn.SchemaName(module=ctx.derived_target_module, name=derived_sname) elif source is scls: derived_sname = scls.get_specialized_name(scls.shortname, *derived_name_quals) derived_name = sn.SchemaName(module='__view__', name=derived_sname) if scls.generic(): derived = scls.derive(ctx.schema, source, target, *qualifiers, name=derived_name, mark_derived=True) else: # If this is already a derived class, reuse its name, # so that the correct storage relations are used in DML. if derived_name is None: derived_name = scls.name derived = scls.derive_copy(ctx.schema, source, target, *qualifiers, name=derived_name, attrs=dict(bases=[scls]), mark_derived=True) if isinstance(derived, s_sources.Source): for pn, ptr in derived.own_pointers.items(): # This is a view of a view. Make sure query-level # computable expressions for pointers are carried over. src_ptr = scls.pointers[pn] computable_data = ctx.source_map.get(src_ptr) if computable_data is not None: ctx.source_map[ptr] = computable_data if isinstance(derived, s_types.Type): if is_insert: vtype = s_types.ViewType.Insert elif is_update: vtype = s_types.ViewType.Update else: vtype = s_types.ViewType.Select derived.view_type = vtype if (add_to_schema and not isinstance(derived, s_types.Collection) and ctx.schema.get(derived.name, None) is None): ctx.schema.add(derived) if isinstance(derived, s_types.Type): ctx.view_nodes[derived.name] = derived return derived
async def read_objtypes(self, schema): tables = await introspection.tables.fetch_tables( self.connection, schema_pattern='edgedb%', table_pattern='%_data') tables = {(t['schema'], t['name']): t for t in tables} objtype_list = await datasources.schema.objtypes.fetch(self.connection) objtype_list = collections.OrderedDict( (sn.Name(row['name']), row) for row in objtype_list) visited_tables = set() self.table_cache.update({ common.objtype_name_to_table_name(n, catenate=False): c for n, c in objtype_list.items() }) basemap = {} for name, row in objtype_list.items(): objtype = { 'name': name, 'title': self.json_to_word_combination(row['title']), 'description': row['description'], '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.ExpressionText(row['expr']) if row['expr'] else None) } table_name = common.objtype_name_to_table_name(name, catenate=False) table = tables.get(table_name) if not table: msg = 'internal metadata incosistency' details = 'Record for type {!r} exists but ' \ 'the table is missing'.format(name) raise s_err.SchemaError(msg, details=details) visited_tables.add(table_name) bases = await self.pg_table_inheritance_to_bases( table['name'], table['schema'], self.table_cache) basemap[name] = bases objtype = s_objtypes.ObjectType(name=name, title=objtype['title'], description=objtype['description'], is_abstract=objtype['is_abstract'], is_final=objtype['is_final'], view_type=objtype['view_type'], expr=objtype['expr']) schema.add(objtype) for objtype in schema.get_objects(type='ObjectType'): try: bases = basemap[objtype.name] except KeyError: pass else: objtype.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['is_derived'] = True objtype = s_objtypes.ObjectType(**attrs) schema.add(objtype) tabdiff = set(tables.keys()) - visited_tables if tabdiff: msg = 'internal metadata incosistency' details = 'Extraneous data tables exist: {}'.format(', '.join( '"%s.%s"' % t for t in tabdiff)) raise s_err.SchemaError(msg, details=details)
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) if view_rptr.ptrcls is None: derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx) 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 if ptrcls in ctx.pending_cardinality: # We do not know the parent's pointer cardinality yet. ptr_cardinality = None else: ptr_cardinality = ptrcls.cardinality 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 = 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) ptr_name = ptrcls.shortname except errors.EdgeQLReferenceError: if is_mutation: raise base_ptrcls = ptrcls = None ptr_module = (ptrname[0] or ctx.derived_target_module or scls.name.module) ptr_name = sn.SchemaName(module=ptr_module, name=ptrname[1]) 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 if is_mutation: shape_expr_ctx.expr_exposed = True irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx) irexpr.context = compexpr.context if base_ptrcls is None: base_ptrcls = 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 = 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 qlexpr is not None or ptr_target is not ptrcls.target: 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 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: if ptr_cardinality is None: if compexpr is not None: ctx.pending_cardinality.add(ptrcls) elif ptrcls is not base_ptrcls: ctx.pointer_derivation_map[base_ptrcls].append(ptrcls) ptrcls.cardinality = None else: ptrcls.cardinality = ptr_cardinality if ptrcls.is_protected_pointer() and qlexpr is not None: msg = f'cannot assign to {ptrcls.shortname.name}' raise errors.EdgeQLError(msg, context=shape_el.context) return ptrcls