def _trace_item_layout( node: qlast.CreateObject, *, obj: Optional[qltracer.NamedObject] = None, fq_name: Optional[s_name.QualName] = None, ctx: LayoutTraceContext, ) -> None: if obj is None: fq_name = ctx.get_local_name(node.name) local_obj = ctx.objects[fq_name] assert isinstance(local_obj, qltracer.NamedObject) obj = local_obj assert fq_name is not None if isinstance(node, qlast.BasesMixin): bases = [] # construct the parents set, used later in ancestors graph parents = set() for ref in _get_bases(node, ctx=ctx): bases.append(ref) # ignore std modules dependencies if ref.get_module_name() not in s_schema.STD_MODULES: parents.add(ref) if ( ref.module not in ctx.local_modules and ref not in ctx.inh_graph ): base_obj = type(obj)(name=ref) ctx.inh_graph[ref] = topological.DepGraphEntry(item=base_obj) base = ctx.schema.get(ref) if isinstance(base, s_sources.Source): assert isinstance(base_obj, qltracer.Source) base_pointers = base.get_pointers(ctx.schema) for pn, p in base_pointers.items(ctx.schema): base_obj.pointers[pn] = qltracer.Pointer( s_name.QualName('__', pn.name), source=base, target=p.get_target(ctx.schema), ) ctx.parents[fq_name] = parents ctx.inh_graph[fq_name] = topological.DepGraphEntry( item=obj, deps=set(bases), merge=set(bases), ) for decl in node.commands: if isinstance(decl, qlast.CreateConcretePointer): assert isinstance(obj, qltracer.Source) target: Optional[qltracer.TypeLike] if isinstance(decl.target, qlast.TypeExpr): target = _resolve_type_expr(decl.target, ctx=ctx) else: target = None pn = s_utils.ast_ref_to_unqualname(decl.name) ptr = qltracer.Pointer( s_name.QualName('__', pn.name), source=obj, target=target, ) obj.pointers[pn] = ptr ptr_name = s_name.QualName( module=fq_name.module, name=f'{fq_name.name}@{decl.name.name}', ) ctx.objects[ptr_name] = ptr ctx.defdeps[fq_name].add(ptr_name) _trace_item_layout( decl, obj=ptr, fq_name=ptr_name, ctx=ctx) elif isinstance(decl, qlast.CreateConcreteConstraint): # Validate that the constraint exists at all. _validate_schema_ref(decl, ctx=ctx) _, con_fq_name = ctx.get_fq_name(decl) con_name = s_name.QualName( module=fq_name.module, name=f'{fq_name.name}@{con_fq_name}', ) ctx.objects[con_name] = qltracer.ConcreteConstraint(con_name) ctx.constraints[fq_name].add(con_name) elif isinstance(decl, qlast.CreateAnnotationValue): # Validate that the constraint exists at all. _validate_schema_ref(decl, ctx=ctx)
def compile_DescribeStmt( ql: qlast.DescribeStmt, *, ctx: context.ContextLevel) -> irast.Set: with ctx.subquery() as ictx: stmt = irast.SelectStmt() init_stmt(stmt, ql, ctx=ictx, parent_ctx=ctx) if ql.object is qlast.DescribeGlobal.Schema: if ql.language is qltypes.DescribeLanguage.DDL: # DESCRIBE SCHEMA text = s_ddl.ddl_text_from_schema( ctx.env.schema, ) else: raise errors.QueryError( f'cannot describe full schema as {ql.language}') ct = typegen.type_to_typeref( ctx.env.get_track_schema_type( s_name.QualName('std', 'str')), env=ctx.env, ) stmt.result = setgen.ensure_set( irast.StringConstant(value=text, typeref=ct), ctx=ictx, ) elif ql.object is qlast.DescribeGlobal.DatabaseConfig: if ql.language is qltypes.DescribeLanguage.DDL: function_call = dispatch.compile( qlast.FunctionCall( func=('cfg', '_describe_database_config_as_ddl'), ), ctx=ictx) assert isinstance(function_call, irast.Set), function_call stmt.result = function_call else: raise errors.QueryError( f'cannot describe config as {ql.language}') elif ql.object is qlast.DescribeGlobal.SystemConfig: if ql.language is qltypes.DescribeLanguage.DDL: function_call = dispatch.compile( qlast.FunctionCall( func=('cfg', '_describe_system_config_as_ddl'), ), ctx=ictx) assert isinstance(function_call, irast.Set), function_call stmt.result = function_call else: raise errors.QueryError( f'cannot describe config as {ql.language}') elif ql.object is qlast.DescribeGlobal.Roles: if ql.language is qltypes.DescribeLanguage.DDL: function_call = dispatch.compile( qlast.FunctionCall( func=('sys', '_describe_roles_as_ddl'), ), ctx=ictx) assert isinstance(function_call, irast.Set), function_call stmt.result = function_call else: raise errors.QueryError( f'cannot describe roles as {ql.language}') else: assert isinstance(ql.object, qlast.ObjectRef), ql.object modules = [] items: DefaultDict[str, List[s_name.Name]] = defaultdict(list) referenced_classes: List[s_obj.ObjectMeta] = [] objref = ql.object itemclass = objref.itemclass if itemclass is qltypes.SchemaObjectClass.MODULE: modules.append(s_utils.ast_ref_to_unqualname(objref)) else: itemtype: Optional[Type[s_obj.Object]] = None name = s_utils.ast_ref_to_name(objref) if itemclass is not None: if itemclass is qltypes.SchemaObjectClass.ALIAS: # Look for underlying derived type. itemtype = s_types.Type else: itemtype = ( s_obj.ObjectMeta.get_schema_metaclass_for_ql_class( itemclass) ) last_exc = None # Search in the current namespace AND in std. We do # this to avoid masking a `std` object/function by one # in a default module. search_ns = [ictx.modaliases] # Only check 'std' separately if the current # modaliases don't already include it. if ictx.modaliases.get(None, 'std') != 'std': search_ns.append({None: 'std'}) # Search in the current namespace AND in std. for aliases in search_ns: # Use the specific modaliases instead of the # context ones. with ictx.subquery() as newctx: newctx.modaliases = aliases # Get the default module name modname = aliases[None] # Is the current item a function is_function = (itemclass is qltypes.SchemaObjectClass.FUNCTION) # We need to check functions if we're looking for them # specifically or if this is a broad search. They are # handled separately because they allow multiple # matches for the same name. if (itemclass is None or is_function): try: funcs: Tuple[s_func.Function, ...] = ( newctx.env.schema.get_functions( name, module_aliases=aliases) ) except errors.InvalidReferenceError: pass else: for func in funcs: items[f'function_{modname}'].append( func.get_name(newctx.env.schema)) # Also find an object matching the name as long as # it's not a function we're looking for specifically. if not is_function: try: if itemclass is not \ qltypes.SchemaObjectClass.ALIAS: condition = None label = None else: condition = ( lambda obj: obj.get_alias_is_persistent( ctx.env.schema ) ) label = 'alias' obj = schemactx.get_schema_object( objref, item_type=itemtype, condition=condition, label=label, ctx=newctx, ) items[f'other_{modname}'].append( obj.get_name(newctx.env.schema)) except errors.InvalidReferenceError as exc: # Record the exception to be possibly # raised if no matches are found last_exc = exc # If we already have some results, suppress the exception, # otherwise raise the recorded exception. if not items and last_exc: raise last_exc verbose = ql.options.get_flag('VERBOSE') method: Any if ql.language is qltypes.DescribeLanguage.DDL: method = s_ddl.ddl_text_from_schema elif ql.language is qltypes.DescribeLanguage.SDL: method = s_ddl.sdl_text_from_schema elif ql.language is qltypes.DescribeLanguage.TEXT: method = s_ddl.descriptive_text_from_schema if not verbose.val: referenced_classes = [s_links.Link, s_lprops.Property] else: raise errors.InternalServerError( f'cannot handle describe language {ql.language}' ) # Based on the items found generate main text and a # potential comment about masked items. defmod = ictx.modaliases.get(None, 'std') default_items = [] masked_items = set() for objtype in ['function', 'other']: defkey = f'{objtype}_{defmod}' mskkey = f'{objtype}_std' default_items += items.get(defkey, []) if defkey in items and mskkey in items: # We have a match in default module and some masked. masked_items.update(items.get(mskkey, [])) else: default_items += items.get(mskkey, []) # Throw out anything in the masked set that's already in # the default. masked_items.difference_update(default_items) text = method( ctx.env.schema, included_modules=modules, included_items=default_items, included_ref_classes=referenced_classes, include_module_ddl=False, include_std_ddl=True, ) if masked_items: text += ('\n\n' '# The following builtins are masked by the above:' '\n\n') masked = method( ctx.env.schema, included_modules=modules, included_items=masked_items, included_ref_classes=referenced_classes, include_module_ddl=False, include_std_ddl=True, ) masked = textwrap.indent(masked, '# ') text += masked ct = typegen.type_to_typeref( ctx.env.get_track_schema_type( s_name.QualName('std', 'str')), env=ctx.env, ) stmt.result = setgen.ensure_set( irast.StringConstant(value=text, typeref=ct), ctx=ictx, ) result = fini_stmt(stmt, ql, ctx=ictx, parent_ctx=ctx) return result
def trace_Path( node: qlast.Path, *, ctx: TracerContext, ) -> Optional[ObjectLike]: tip: Optional[ObjectLike] = None ptr: Optional[Union[Pointer, s_pointers.Pointer]] = None plen = len(node.steps) for i, step in enumerate(node.steps): if isinstance(step, qlast.ObjectRef): # the ObjectRef without a module may be referring to an # aliased expression aname = sn.QualName('__alias__', step.name) if not step.module and aname in ctx.objects: tip = ctx.objects[aname] else: refname = ctx.get_ref_name(step) if refname in ctx.objects: ctx.refs.add(refname) tip = ctx.objects[refname] else: tip = ctx.schema.get(refname) elif isinstance(step, qlast.Ptr): if i == 0: # Abbreviated path. if ctx.path_prefix in ctx.objects: tip = ctx.objects[ctx.path_prefix] if isinstance(tip, Pointer): ptr = tip else: # We can't reason about this path. return None if step.type == 'property': if ptr is None: # This is either a computable def, or # unknown link, bail. return None elif isinstance(ptr, (s_links.Link, Pointer)): lprop = ptr.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(step.ptr), ) if lprop is None: # Invalid link property reference, bail. return None if (isinstance(lprop, Pointer) and lprop.source is not None): src = lprop.source src_name = src.get_name(ctx.schema) if (isinstance(src, Pointer) and src.source is not None): src_src_name = src.source.get_name(ctx.schema) source_name = qualify_name(src_src_name, src_name.name) else: source_name = src_name ctx.refs.add(qualify_name(source_name, step.ptr.name)) else: if step.direction == '<': if plen > i + 1 and isinstance(node.steps[i + 1], qlast.TypeIntersection): # A reverse link traversal with a type intersection, # process it on the next step. pass else: # No type intersection, so the only type that # it can be is "Object", which is trivial. # However, we need to make it dependent on # every link of the same name now. for fqname, obj in ctx.objects.items(): # Ignore what appears to not be a link # with the right name. if (isinstance(obj, (s_pointers.Pointer, Pointer)) and fqname.name.split( '@', 1)[1] == step.ptr.name): target = obj.get_target(ctx.schema) # Ignore scalars, but include other # computables to produce better error # messages. if (target is None or not target.is_scalar()): # Record link with matching short # name. ctx.refs.add(fqname) return None else: if isinstance(tip, (Source, s_sources.Source)): ptr = tip.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(step.ptr), ) if ptr is None: # Invalid pointer reference, bail. return None else: ptr_source = ptr.get_source(ctx.schema) if ptr_source is not None: sname = ptr_source.get_name(ctx.schema) assert isinstance(sname, sn.QualName) ctx.refs.add(qualify_name(sname, step.ptr.name)) tip = ptr.get_target(ctx.schema) if tip is None: if ptr in ctx.visited: # Possibly recursive definition, bail out. return None # This can only be Pointer that didn't # infer the target type yet. assert isinstance(ptr, Pointer) # We haven't computed the target yet, # so try computing it now. ctx.visited.add(ptr) ptr_target = trace(ptr.target_expr, ctx=ctx) if isinstance(ptr_target, (Type, s_types.Type)): tip = ptr.target = ptr_target else: # Can't figure out the new tip, so we bail. return None else: # We can't reason about this path. return None elif isinstance(step, qlast.TypeIntersection): # This tip is determined from the type in the type # intersection, which is valid in the general case, but # there's a special case that needs to be potentially # handled for backward links. tip = _resolve_type_expr(step.type, ctx=ctx) prev_step = node.steps[i - 1] if isinstance(prev_step, qlast.Ptr): if prev_step.direction == '<': if isinstance(tip, (s_sources.Source, ObjectType)): ptr = tip.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(prev_step.ptr), ) if ptr is None: # Invalid pointer reference, bail. return None if isinstance(tip, Type): tip_name = tip.get_name(ctx.schema) ctx.refs.add( qualify_name(tip_name, prev_step.ptr.name)) # This is a backwards link, so we need the source. tip = ptr.get_source(ctx.schema) else: tr = trace(step, ctx=ctx) if tr is not None: tip = tr if isinstance(tip, Pointer): ptr = tip return tip
def trace_Path( node: qlast.Path, *, ctx: TracerContext, ) -> Optional[ObjectLike]: tip: Optional[ObjectLike] = None ptr: Optional[Union[Pointer, s_pointers.Pointer]] = None plen = len(node.steps) for i, step in enumerate(node.steps): if isinstance(step, qlast.ObjectRef): # the ObjectRef without a module may be referring to an # aliased expression aname = sn.QualName('__alias__', step.name) if not step.module and aname in ctx.objects: tip = ctx.objects[aname] else: refname = ctx.get_ref_name(step) if refname in ctx.objects: ctx.refs.add(refname) tip = ctx.objects[refname] else: tip = ctx.schema.get(refname) elif isinstance(step, qlast.Ptr): if i == 0: # Abbreviated path. if ctx.path_prefix in ctx.objects: tip = ctx.objects[ctx.path_prefix] else: # We can't reason about this path. return None if step.type == 'property': if ptr is None: # This is either a computable def, or # unknown link, bail. return None elif isinstance(ptr, (s_links.Link, Pointer)): lprop = ptr.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(step.ptr), ) if lprop is None: # Invalid link property reference, bail. return None if (isinstance(lprop, Pointer) and lprop.source is not None): src = lprop.source src_name = src.get_name(ctx.schema) if (isinstance(src, Pointer) and src.source is not None): src_src_name = src.source.get_name(ctx.schema) source_name = qualify_name( src_src_name, src_name.name) else: source_name = src_name ctx.refs.add(qualify_name(source_name, step.ptr.name)) else: if step.direction == '<': if plen > i + 1 and isinstance(node.steps[i + 1], qlast.TypeIntersection): # A reverse link traversal with a type intersection, # process it on the next step. pass else: # otherwise we cannot say anything about the target, # so bail. return None else: if isinstance(tip, (Source, s_sources.Source)): ptr = tip.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(step.ptr), ) if ptr is None: # Invalid pointer reference, bail. return None else: ptr_source = ptr.get_source(ctx.schema) if ptr_source is not None: sname = ptr_source.get_name(ctx.schema) assert isinstance(sname, sn.QualName) ctx.refs.add(qualify_name(sname, step.ptr.name)) tip = ptr.get_target(ctx.schema) else: # Can't figure out the new tip, so we bail. return None else: # We can't reason about this path. return None elif isinstance(step, qlast.TypeIntersection): # This tip is determined from the type in the type # intersection, which is valid in the general case, but # there's a special case that needs to be potentially # handled for backward links. tip = _resolve_type_expr(step.type, ctx=ctx) prev_step = node.steps[i - 1] if isinstance(prev_step, qlast.Ptr): if prev_step.direction == '<': if isinstance(tip, (s_sources.Source, ObjectType)): ptr = tip.maybe_get_ptr( ctx.schema, s_utils.ast_ref_to_unqualname(prev_step.ptr), ) if ptr is None: # Invalid pointer reference, bail. return None if isinstance(tip, Type): tip_name = tip.get_name(ctx.schema) ctx.refs.add(qualify_name( tip_name, prev_step.ptr.name)) # This is a backwards link, so we need the source. tip = ptr.get_source(ctx.schema) else: tr = trace(step, ctx=ctx) if tr is not None: tip = tr if isinstance(tip, Pointer): ptr = tip return tip