def _get_general_offset_limit(self, after, before, first, last): # convert any static values to corresponding qlast if after is not None: if isinstance(after, qlast.Base): after = qlast.TypeCast( type=qlast.TypeName( maintype=qlast.ObjectRef(name='int64')), expr=after ) else: after = qlast.BaseConstant.from_python(after) if before is not None: if isinstance(before, qlast.Base): before = qlast.TypeCast( type=qlast.TypeName( maintype=qlast.ObjectRef(name='int64')), expr=before ) else: before = qlast.BaseConstant.from_python(before) if first is not None and not isinstance(first, qlast.Base): first = qlast.BaseConstant.from_python(first) if last is not None and not isinstance(last, qlast.Base): last = qlast.BaseConstant.from_python(last) offset = limit = None # convert before, after, first and last into offset and limit if after is not None: # The +1 is to make 'after' into an appropriate index. # # 0--a--1--b--2--c--3-- ... we call element at # index 0 (or "element 0" for short), the element # immediately after the mark 0. So after "element # 0" really means after "index 1". offset = qlast.BinOp( left=after, op='+', right=qlast.IntegerConstant(value='1') ) if before is not None: # limit = before - (after or 0) if after: limit = qlast.BinOp( left=before, op='-', right=after ) else: limit = before if first is not None: if limit is None: limit = first else: limit = qlast.IfElse( if_expr=first, condition=qlast.BinOp( left=first, op='<', right=limit ), else_expr=limit ) if last is not None: if limit is not None: if offset: offset = qlast.BinOp( left=offset, op='+', right=qlast.BinOp( left=limit, op='-', right=last ) ) else: offset = qlast.BinOp( left=limit, op='-', right=last ) limit = qlast.IfElse( if_expr=last, condition=qlast.BinOp( left=last, op='<', right=limit ), else_expr=limit ) else: # FIXME: there wasn't any limit, so we can define last # in terms of offset alone without negative OFFSET # implementation raise g_errors.GraphQLTranslationError( f'last translates to a negative OFFSET in ' f'EdgeQL which is currently unsupported') return offset, limit
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 _process_view( *, stype: s_objtypes.ObjectType, path_id: irast.PathId, path_id_namespace: Optional[irast.WeakNamespace] = None, elements: List[qlast.ShapeElement], view_rptr: Optional[context.ViewRPtr] = None, view_name: Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, is_delete: bool = False, parser_context: pctx.ParserContext, ctx: context.ContextLevel, ) -> s_objtypes.ObjectType: if (view_name is None and ctx.env.options.schema_view_mode and view_rptr is not None): # Make sure persistent schema expression aliases have properly formed # names as opposed to the usual mangled form of the ephemeral # aliases. This is needed for introspection readability, as well # as helps in maintaining proper type names for schema # representations that require alphanumeric names, such as # GraphQL. # # We use the name of the source together with the name # of the inbound link to form the name, so in e.g. # CREATE ALIAS V := (SELECT Foo { bar: { baz: { ... } }) # The name of the innermost alias would be "__V__bar__baz". source_name = view_rptr.source.get_name(ctx.env.schema).name if not source_name.startswith('__'): source_name = f'__{source_name}' if view_rptr.ptrcls_name is not None: ptr_name = view_rptr.ptrcls_name.name elif view_rptr.ptrcls is not None: ptr_name = view_rptr.ptrcls.get_shortname(ctx.env.schema).name else: raise errors.InternalServerError( '_process_view in schema mode received view_rptr with ' 'neither ptrcls_name, not ptrcls' ) name = f'{source_name}__{ptr_name}' view_name = sn.Name( module=ctx.derived_target_module or '__derived__', name=name, ) view_scls = schemactx.derive_view( stype, is_insert=is_insert, is_update=is_update, is_delete=is_delete, derived_name=view_name, ctx=ctx, ) assert isinstance(view_scls, s_objtypes.ObjectType), view_scls is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation if view_rptr is not None and view_rptr.ptrcls is None: derive_ptrcls( view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointer = _normalize_view_ptr_expr( shape_el, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx) if pointer in pointers: schema = ctx.env.schema vnp = pointer.get_verbosename(schema, with_parent=True) raise errors.QueryError( f'duplicate definition of {vnp}', context=shape_el.context) pointers.append(pointer) if is_insert: explicit_ptrs = { ptrcls.get_shortname(ctx.env.schema).name for ptrcls in pointers } scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue default_expr = ptrcls.get_default(ctx.env.schema) if not default_expr: if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): # If the target is a sequence, there's no need # for an explicit value. ptrcls_target = ptrcls.get_target(ctx.env.schema) assert ptrcls_target is not None if ptrcls_target.issubclass( ctx.env.schema, ctx.env.schema.get('std::sequence')): continue what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') else: continue ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) default_ql = qlast.ShapeElement( expr=qlast.Path( steps=[ qlast.Ptr( ptr=qlast.ObjectRef( name=ptrcls_sn.name, module=ptrcls_sn.module, ), ), ], ), compexpr=qlast.DetachedExpr( expr=default_expr.qlast, ), ) with ctx.newscope(fenced=True) as scopectx: pointers.append( _normalize_view_ptr_expr( default_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, from_default=True, view_rptr=view_rptr, ctx=scopectx, ), ) elif ( stype.get_name(ctx.env.schema).module == 'schema' and ctx.env.options.introspection_schema_rewrites ): explicit_ptrs = { ptrcls.get_shortname(ctx.env.schema).name for ptrcls in pointers } scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if ( pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema) ): continue schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is None: continue with ctx.newscope(fenced=True) as scopectx: implicit_ql = qlast.ShapeElement( expr=qlast.Path( steps=[ qlast.Ptr( ptr=qlast.ObjectRef( name=pn, ), ), ], ), compexpr=qlast.BinOp( left=qlast.Path( partial=True, steps=[ qlast.Ptr( ptr=qlast.ObjectRef(name=pn), direction=( s_pointers.PointerDirection.Outbound ), ) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ), ) # Note: we only need to record the schema default # as a computable, but not include it in the type # shape, so we ignore the return value. _normalize_view_ptr_expr( implicit_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx, ) for ptrcls in pointers: source: Union[s_types.Type, s_pointers.PointerLike] if ptrcls.is_link_property(ctx.env.schema): assert view_rptr is not None and view_rptr.ptrcls is not None source = view_rptr.ptrcls else: source = view_scls if is_defining_shape: cinfo = ctx.source_map.get(ptrcls) if cinfo is not None: shape_op = cinfo.shape_op else: shape_op = qlast.ShapeOp.ASSIGN ctx.env.view_shapes[source].append((ptrcls, shape_op)) if (view_rptr is not None and view_rptr.ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value( ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def sdl_to_ddl( schema: s_schema.Schema, documents: Mapping[str, List[qlast.DDL]], ) -> Tuple[qlast.DDLCommand, ...]: ddlgraph: DDLGraph = {} mods: List[qlast.DDLCommand] = [] ctx = LayoutTraceContext( schema, local_modules=frozenset(mod for mod in documents), ) for module_name, declarations in documents.items(): ctx.set_module(module_name) for decl_ast in declarations: if isinstance(decl_ast, qlast.CreateObject): _, fq_name = ctx.get_fq_name(decl_ast) if isinstance(decl_ast, (qlast.CreateObjectType, qlast.CreateAlias)): ctx.objects[fq_name] = qltracer.ObjectType(fq_name) elif isinstance(decl_ast, qlast.CreateScalarType): ctx.objects[fq_name] = qltracer.Type(fq_name) elif isinstance(decl_ast, (qlast.CreateLink, qlast.CreateProperty)): ctx.objects[fq_name] = qltracer.Pointer(fq_name, source=None, target=None) elif isinstance(decl_ast, qlast.CreateFunction): ctx.objects[fq_name] = qltracer.Function(fq_name) elif isinstance(decl_ast, qlast.CreateConstraint): ctx.objects[fq_name] = qltracer.Constraint(fq_name) elif isinstance(decl_ast, qlast.CreateAnnotation): ctx.objects[fq_name] = qltracer.Annotation(fq_name) else: raise AssertionError( f'unexpected SDL declaration: {decl_ast}') for module_name, declarations in documents.items(): ctx.set_module(module_name) for decl_ast in declarations: trace_layout(decl_ast, ctx=ctx) # compute the ancestors graph for obj_name in ctx.parents.keys(): ctx.ancestors[obj_name] = get_ancestors(obj_name, ctx.ancestors, ctx.parents) topological.normalize( ctx.inh_graph, merger=_graph_merge_cb, # type: ignore schema=schema, ) tracectx = DepTraceContext(schema, ddlgraph, ctx.objects, ctx.parents, ctx.ancestors, ctx.defdeps, ctx.constraints) for module_name, declarations in documents.items(): tracectx.set_module(module_name) # module needs to be created regardless of whether its # contents are empty or not mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name))) for decl_ast in declarations: trace_dependencies(decl_ast, ctx=tracectx) ordered = topological.sort(ddlgraph, allow_unresolved=False) return tuple(mods) + tuple(ordered)
def reduce_AnyIdentifier(self, *kids): self.val = qlast.ObjectRef(module=None, name=kids[0].val)
def compile_result_clause( result: qlast.Base, *, view_scls: typing.Optional[s_types.Type]=None, view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[s_name.SchemaName]=None, result_alias: typing.Optional[str]=None, forward_rptr: bool=False, ctx: context.ContextLevel) -> irast.Set: with ctx.new() as sctx: sctx.clause = 'result' if sctx.stmt is ctx.toplevel_stmt: sctx.toplevel_clause = sctx.clause sctx.expr_exposed = True if forward_rptr: sctx.view_rptr = view_rptr # sctx.view_scls = view_scls if isinstance(result, qlast.Shape): result_expr = result.expr shape = result.elements else: result_expr = result shape = None if result_alias: # `SELECT foo := expr` is largely equivalent to # `WITH foo := expr SELECT foo` with one important exception: # the scope namespace does not get added to the current query # path scope. This is needed to handle FOR queries correctly. with sctx.newscope(temporary=True, fenced=True) as scopectx: stmtctx.declare_view( result_expr, alias=result_alias, temporary_scope=False, ctx=scopectx) result_expr = qlast.Path( steps=[qlast.ObjectRef(name=result_alias)] ) if (view_rptr is not None and (view_rptr.is_insert or view_rptr.is_update) and view_rptr.ptrcls is not None) and False: # If we have an empty set assigned to a pointer in an INSERT # or UPDATE, there's no need to explicitly specify the # empty set type and it can be assumed to match the pointer # target type. target_t = view_rptr.ptrcls.get_target(ctx.env.schema) if astutils.is_ql_empty_set(result_expr): expr = setgen.new_empty_set( stype=target_t, alias=ctx.aliases.get('e'), ctx=sctx, srcctx=result_expr.context, ) else: with sctx.new() as exprctx: exprctx.empty_result_type_hint = target_t expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=exprctx), ctx=exprctx) else: if astutils.is_ql_empty_set(result_expr): expr = setgen.new_empty_set( stype=sctx.empty_result_type_hint, alias=ctx.aliases.get('e'), ctx=sctx, srcctx=result_expr.context, ) else: expr = setgen.ensure_set( dispatch.compile(result_expr, ctx=sctx), ctx=sctx) result = compile_query_subject( expr, shape=shape, view_rptr=view_rptr, view_name=view_name, result_alias=result_alias, view_scls=view_scls, compile_views=ctx.stmt is ctx.toplevel_stmt, ctx=sctx) ctx.partial_path_prefix = result return result
def reduce_INDEX_OnExpr(self, *kids): self.val = qlast.CreateIndex( name=qlast.ObjectRef(name='idx'), expr=kids[1].val, )
def reduce_DUNDERTYPE(self, *kids): self.val = qlast.ObjectRef(name=kids[0].val)
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 _process_view(*, stype: s_nodes.Node, path_id: irast.PathId, path_id_namespace: typing.Optional[ irast.WeakNamespace] = None, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr] = None, view_name: typing.Optional[sn.SchemaName] = None, is_insert: bool = False, is_update: bool = False, ctx: context.CompilerContext) -> s_nodes.Node: view_scls = schemactx.derive_view(stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointers.append( _normalize_view_ptr_expr(shape_el, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) if is_insert: explicit_ptrs = { ptrcls.get_shortname(ctx.env.schema).name for ptrcls in pointers } scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue if not ptrcls.get_default(ctx.env.schema): if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') else: continue ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls_sn.name, module=ptrcls_sn.module)) ])) with ctx.newscope(fenced=True) as scopectx: pointers.append( _normalize_view_ptr_expr( default_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) for ptrcls in pointers: if ptrcls.is_link_property(ctx.env.schema): source = view_rptr.derived_ptrcls else: source = view_scls if ptrcls.get_source(ctx.env.schema) is source: ctx.env.schema = source.add_pointer(ctx.env.schema, ptrcls, replace=True) if is_defining_shape: if source is None: # The nested shape is merely selecting the pointer, # so the link class has not been derived. But for # the purposes of shape tracking, we must derive it # still. The derived pointer must be treated the same # as the original, as this is not a new computable, # and both `Foo.ptr` and `Foo { ptr }` are the same path, # hence the `transparent` modifier. source = derive_ptrcls(view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) ctx.env.view_shapes[source].append(ptrcls) if (view_rptr is not None and view_rptr.derived_ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr', view_rptr.derived_ptrcls) return view_scls
def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: typing.Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Array) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0] el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx) if el_cast is not None and el_cast.get_from_cast(ctx.env.schema): # Simple cast return _cast_to_ir(ir_set, el_cast, orig_stype, new_stype, ctx=ctx) else: pathctx.register_set_in_scope(ir_set, ctx=ctx) with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_alias = subctx.aliases.get('a') subctx.anchors[source_alias] = ir_set unpacked = qlast.FunctionCall( func=('std', 'array_unpack'), args=[ qlast.Path(steps=[qlast.ObjectRef(name=source_alias)], ), ], ) elements = qlast.FunctionCall( func=('std', 'array_agg'), args=[ qlast.TypeCast( expr=unpacked, type=typegen.type_to_ql_typeref(el_type, ctx=subctx), ), ], ) array_ir = dispatch.compile(elements, ctx=subctx) assert isinstance(array_ir, irast.Set) if direct_cast is not None: array_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) return _cast_to_ir(array_ir, direct_cast, array_stype, new_stype, ctx=ctx) else: return array_ir
def sdl_to_ddl(schema, documents): ddlgraph = {} mods = [] ctx = LayoutTraceContext( schema, local_modules=frozenset(mod for mod, schema_decl in documents), ) for module_name, declarations in documents: ctx.set_module(module_name) for decl_ast in declarations: if isinstance(decl_ast, qlast.CreateObject): _, fq_name = ctx.get_fq_name(decl_ast) if isinstance(decl_ast, (qlast.CreateObjectType, qlast.CreateAlias)): ctx.objects[fq_name] = qltracer.ObjectType(fq_name) elif isinstance(decl_ast, qlast.CreateScalarType): ctx.objects[fq_name] = qltracer.Type(fq_name) elif isinstance(decl_ast, (qlast.CreateLink, qlast.CreateProperty)): ctx.objects[fq_name] = qltracer.Pointer( fq_name, source=None, target=None) elif isinstance(decl_ast, qlast.CreateFunction): ctx.objects[fq_name] = qltracer.Function(fq_name) elif isinstance(decl_ast, qlast.CreateConstraint): ctx.objects[fq_name] = qltracer.Constraint(fq_name) elif isinstance(decl_ast, qlast.CreateAnnotation): ctx.objects[fq_name] = qltracer.Annotation(fq_name) else: raise AssertionError( f'unexpected SDL declaration: {decl_ast}') for module_name, declarations in documents: ctx.set_module(module_name) for decl_ast in declarations: trace_layout(decl_ast, ctx=ctx) # compute the ancestors graph for fq_name in ctx.parents.keys(): ctx.ancestors[fq_name] = get_ancestors( fq_name, ctx.ancestors, ctx.parents) topological.normalize(ctx.inh_graph, _merge_items) ctx = DepTraceContext( schema, ddlgraph, ctx.objects, ctx.parents, ctx.ancestors, ctx.defdeps ) for module_name, declarations in documents: ctx.set_module(module_name) # module needs to be created regardless of whether its # contents are empty or not mods.append(qlast.CreateModule( name=qlast.ObjectRef(name=module_name))) for decl_ast in declarations: trace_dependencies(decl_ast, ctx=ctx) return mods + list(topological.sort(ddlgraph, allow_unresolved=False))
def _process_view( *, stype: s_types.Type, path_id: irast.PathId, path_id_namespace: typing.Optional[irast.WeakNamespace]=None, elements: typing.List[qlast.ShapeElement], view_rptr: typing.Optional[context.ViewRPtr]=None, view_name: typing.Optional[sn.SchemaName]=None, is_insert: bool=False, is_update: bool=False, ctx: context.ContextLevel) -> s_types.Type: if (view_name is None and ctx.env.schema_view_mode and view_rptr is not None): # Make sure persistent schema views have properly formed # names as opposed to the usual mangled form of the ephemeral # views. This is needed for introspection readability, as well # as helps in maintaining proper type names for schema # representations that require alphanumeric names, such as # GraphQL. # # We use the name of the source together with the name # of the inbound link to form the name, so in e.g. # CREATE VIEW V := (SELECT Foo { bar: { baz: { ... } }) # The name of the innermost view would be "__V__bar__baz". source_name = view_rptr.source.get_name(ctx.env.schema).name if not source_name.startswith('__'): source_name = f'__{source_name}' if view_rptr.ptrcls_name is not None: ptr_name = view_rptr.ptrcls_name.name elif view_rptr.ptrcls is not None: ptr_name = view_rptr.ptrcls.get_shortname(ctx.env.schema).name else: raise errors.InternalServerError( '_process_view in schema mode received view_rptr with ' 'neither ptrcls_name, not ptrcls' ) name = f'{source_name}__{ptr_name}' view_name = sn.Name( module=ctx.derived_target_module or '__derived__', name=name, ) view_scls = schemactx.derive_view( stype, is_insert=is_insert, is_update=is_update, derived_name=view_name, ctx=ctx) is_mutation = is_insert or is_update is_defining_shape = ctx.expr_exposed or is_mutation if view_rptr is not None and view_rptr.ptrcls is None: derive_ptrcls( view_rptr, target_scls=view_scls, transparent=True, ctx=ctx) pointers = [] for shape_el in elements: with ctx.newscope(fenced=True) as scopectx: pointers.append(_normalize_view_ptr_expr( shape_el, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) if is_insert: assert isinstance(stype, s_objtypes.ObjectType) explicit_ptrs = {ptrcls.get_shortname(ctx.env.schema).name for ptrcls in pointers} scls_pointers = stype.get_pointers(ctx.env.schema) for pn, ptrcls in scls_pointers.items(ctx.env.schema): if (pn in explicit_ptrs or ptrcls.is_pure_computable(ctx.env.schema)): continue if not ptrcls.get_default(ctx.env.schema): if ptrcls.get_required(ctx.env.schema): if ptrcls.is_property(ctx.env.schema): what = 'property' else: what = 'link' raise errors.MissingRequiredError( f'missing value for required {what} ' f'{stype.get_displayname(ctx.env.schema)}.' f'{ptrcls.get_displayname(ctx.env.schema)}') else: continue ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls_sn.name, module=ptrcls_sn.module)) ])) with ctx.newscope(fenced=True) as scopectx: pointers.append(_normalize_view_ptr_expr( default_ql, view_scls, path_id=path_id, path_id_namespace=path_id_namespace, is_insert=is_insert, is_update=is_update, view_rptr=view_rptr, ctx=scopectx)) for ptrcls in pointers: if ptrcls.is_link_property(ctx.env.schema): assert view_rptr is not None source = view_rptr.ptrcls else: source = view_scls if is_defining_shape: ctx.env.view_shapes[source].append(ptrcls) if (view_rptr is not None and view_rptr.ptrcls is not None and view_scls is not stype): ctx.env.schema = view_scls.set_field_value( ctx.env.schema, 'rptr', view_rptr.ptrcls) return view_scls
def reduce_DOT_ICONST(self, *kids): # this is a valid link-like syntax for accessing unnamed tuples from edb.schema import pointers as s_pointers self.val = qlast.Ptr(ptr=qlast.ObjectRef(name=kids[1].val), direction=s_pointers.PointerDirection.Outbound)
def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Array) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0] el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx) if el_cast is not None and el_cast.get_from_cast(ctx.env.schema): # Simple cast return _cast_to_ir(ir_set, el_cast, orig_stype, new_stype, ctx=ctx) else: pathctx.register_set_in_scope(ir_set, ctx=ctx) with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_alias = subctx.aliases.get('a') subctx.anchors[source_alias] = ir_set unpacked = qlast.FunctionCall( func=('__std__', 'array_unpack'), args=[ qlast.Path(steps=[qlast.ObjectRef(name=source_alias)], ), ], ) enumerated = setgen.ensure_set( dispatch.compile( qlast.FunctionCall( func=('__std__', 'enumerate'), args=[unpacked], ), ctx=subctx, ), ctx=subctx, ) enumerated_alias = subctx.aliases.get('e') subctx.anchors[enumerated_alias] = enumerated enumerated_ref = qlast.Path( steps=[qlast.ObjectRef(name=enumerated_alias)], ) elements = qlast.FunctionCall( func=('__std__', 'array_agg'), args=[ qlast.SelectQuery( result=qlast.TypeCast( expr=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='1', direction='>', ), ), ], ), type=typegen.type_to_ql_typeref( el_type, ctx=subctx, ), cardinality_mod=qlast.CardinalityModifier.Required, ), orderby=[ qlast.SortExpr( path=qlast.Path(steps=[ enumerated_ref, qlast.Ptr(ptr=qlast.ObjectRef( name='0', direction='>', ), ), ], ), direction=qlast.SortOrder.Asc, ), ], ), ], ) if el_type.contains_json(subctx.env.schema): subctx.inhibit_implicit_limit = True array_ir = dispatch.compile(elements, ctx=subctx) assert isinstance(array_ir, irast.Set) if direct_cast is not None: ctx.env.schema, array_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) return _cast_to_ir(array_ir, direct_cast, array_stype, new_stype, ctx=ctx) else: return array_ir
def reduce_Identifier(self, *kids): self.val = qlast.Path(steps=[qlast.ObjectRef(name=kids[0].val)])
def name_path(name: str) -> qlast.Path: return qlast.Path(steps=[qlast.ObjectRef(name=name)])
def reduce_BaseName(self, *kids): self.val = qlast.ObjectRef(module='.'.join(kids[0].val[:-1]) or None, name=kids[0].val[-1])
def typeref_to_ast( schema: s_schema.Schema, ref: Union[so.Object, so.ObjectShell], *, _name: Optional[str] = None, disambiguate_std: bool = False, ) -> qlast.TypeExpr: from . import types as s_types if isinstance(ref, so.ObjectShell): return shell_to_ast(schema, ref) else: t = ref result: qlast.TypeExpr components: Tuple[so.Object, ...] if t.is_type() and cast(s_types.Type, t).is_any(schema): result = qlast.TypeName(name=_name, maintype=qlast.AnyType()) elif t.is_type() and cast(s_types.Type, t).is_anytuple(schema): result = qlast.TypeName(name=_name, maintype=qlast.AnyTuple()) elif isinstance(t, s_types.Tuple) and t.is_named(schema): result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ typeref_to_ast( schema, st, _name=sn, disambiguate_std=disambiguate_std) for sn, st in t.iter_subtypes(schema) ]) elif isinstance(t, (s_types.Array, s_types.Tuple)): # Here the concrete type Array is used because t.schema_name is used, # which is not defined for more generic collections and abcs result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ typeref_to_ast( schema, st, disambiguate_std=disambiguate_std) for st in t.get_subtypes(schema) ]) elif t.is_type() and cast(s_types.Type, t).is_union_type(schema): object_set: Optional[so.ObjectSet[s_types.Type]] = \ cast(s_types.Type, t).get_union_of(schema) assert object_set is not None component_objects = tuple(object_set.objects(schema)) result = typeref_to_ast(schema, component_objects[0], disambiguate_std=disambiguate_std) for component_object in component_objects[1:]: result = qlast.TypeOp( left=result, op='|', right=typeref_to_ast(schema, component_object, disambiguate_std=disambiguate_std), ) elif isinstance(t, so.QualifiedObject): t_name = t.get_name(schema) module = t_name.module if disambiguate_std and module == 'std': # If the type is defined in 'std::', replace the module to # '__std__' to handle cases where 'std' name is aliased to # another module. module = '__std__' result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(module=module, name=t_name.name)) else: raise NotImplementedError(f'cannot represent {t!r} as a shell') return result
def reduce_NodeName(self, *kids): self.val = qlast.Path(steps=[ qlast.ObjectRef(name=kids[0].val.name, module=kids[0].val.module) ])
def shell_to_ast( schema: s_schema.Schema, t: so.ObjectShell, *, _name: Optional[str] = None, ) -> qlast.TypeExpr: from . import pseudo as s_pseudo from . import types as s_types result: qlast.TypeExpr qlref: qlast.BaseObjectRef if isinstance(t, s_pseudo.PseudoTypeShell): if t.name == 'anytype': qlref = qlast.AnyType() elif t.name == 'anytuple': qlref = qlast.AnyTuple() else: raise AssertionError(f'unexpected pseudo type shell: {t.name!r}') result = qlast.TypeName(name=_name, maintype=qlref) elif isinstance(t, s_types.TupleTypeShell): if t.is_named(): result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name='tuple', ), subtypes=[ shell_to_ast(schema, st, _name=sn) for sn, st in t.iter_subtypes(schema) ]) else: result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name='tuple', ), subtypes=[ shell_to_ast(schema, st) for st in t.get_subtypes(schema) ]) elif isinstance(t, s_types.ArrayTypeShell): result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name='array', ), subtypes=[ shell_to_ast(schema, st) for st in t.get_subtypes(schema) ]) elif isinstance(t, s_types.UnionTypeShell): components = t.get_components(schema) result = typeref_to_ast(schema, components[0]) for component in components[1:]: result = qlast.TypeOp( left=result, op='|', right=typeref_to_ast(schema, component), ) elif isinstance(t, so.ObjectShell): name = t.name if isinstance(name, sn.SchemaName): qlref = qlast.ObjectRef( module=name.module, name=name.name, ) else: qlref = qlast.ObjectRef( module='', name=name, ) result = qlast.TypeName( name=_name, maintype=qlref, ) else: raise NotImplementedError(f'cannot represent {t!r} as a shell') return result
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 visit_Field(self, node): if self._is_duplicate_field(node): return is_top, path, prevt, target, steps = \ self._prepare_field(node) json_mode = False is_shadowed = prevt.is_field_shadowed(node.name.value) # determine if there needs to be extra subqueries if not prevt.dummy and target.dummy: json_mode = True # this is a special introspection type eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef( name=(node.alias or node.name).value ) )] ), compexpr=eql, ) elif is_shadowed and not node.alias: # shadowed field that doesn't need an alias spec = filterable = shape = qlast.ShapeElement( expr=qlast.Path(steps=steps), ) elif not node.selection_set or is_shadowed and node.alias: # this is either an unshadowed terminal field or an aliased # shadowed field prefix = qlast.Path(steps=self.get_path_prefix(-1)) eql, shape, filterable = prevt.get_field_template( node.name.value, parent=prefix, has_shape=bool(node.selection_set) ) spec = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef( # this is already a sub-query name=(node.alias or node.name).value ) )] ), compexpr=eql, # preserve the original cardinality of the computable # aliased fields cardinality=prevt.get_field_cardinality(node.name.value), ) else: # if the parent is NOT a shadowed type, we need an explicit SELECT eql, shape, filterable = target.get_template() spec = qlast.ShapeElement( expr=qlast.Path( steps=[qlast.Ptr( ptr=qlast.ObjectRef( # this is already a sub-query name=(node.alias or node.name).value ) )] ), compexpr=eql, # preserve the original cardinality of the computable, # which is basically one of the top-level query # fields, all of which are returning lists cardinality=qltypes.Cardinality.MANY, ) if node.selection_set is not None: if not json_mode: # a single recursion target, so we can process # selection set now self._context.fields.append({}) vals = self.visit(node.selection_set) self._context.fields.pop() if shape: shape.elements = vals if filterable: where, orderby, offset, limit = \ self._visit_arguments(node.arguments) filterable.where = where filterable.orderby = orderby filterable.offset = offset filterable.limit = limit path.pop() return spec
def reduce_INDEX_OnExpr_CreateIndexSDLCommandsBlock(self, *kids): self.val = qlast.CreateIndex( name=qlast.ObjectRef(name='idx'), expr=kids[1].val, commands=kids[2].val, )
def typeref_to_ast(schema: s_schema.Schema, t: so.Object, *, _name: Optional[str] = None) -> qlast.TypeExpr: from . import types as s_types if isinstance(t, so.ObjectRef): # We want typenames like 'anytype` that are wrapped in an # ObjectRef to be unwrapped to proper types, so that we # can generate proper AST nodes for them (e.g. for `anytype` it # is `qlast.AnyType()`). t = t._resolve_ref(schema) result: qlast.TypeExpr components: Tuple[so.ObjectRef, ...] if t.is_type() and cast(s_types.Type, t).is_any(): result = qlast.TypeName(name=_name, maintype=qlast.AnyType()) elif t.is_type() and cast(s_types.Type, t).is_anytuple(): result = qlast.TypeName(name=_name, maintype=qlast.AnyTuple()) elif isinstance(t, s_types.Tuple) and t.named: result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ typeref_to_ast(schema, cast(so.ObjectRef, st), _name=sn) for sn, st in t.iter_subtypes(schema) ]) elif isinstance(t, (s_types.Array, s_types.Tuple)): # Here the concrete type Array is used because t.schema_name is used, # which is not defined for more generic collections and abcs result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ typeref_to_ast(schema, st) for st in t.get_subtypes(schema) ]) elif isinstance(t, s_types.UnionTypeRef): components = t.get_union_of(schema) result = typeref_to_ast(schema, components[0]) for component in components[1:]: result = qlast.TypeOp( left=result, op='|', right=typeref_to_ast(schema, component), ) elif t.is_type() and cast(s_types.Type, t).is_union_type(schema): object_set: Optional[so.ObjectSet[s_types.Type]] = \ cast(s_types.Type, t).get_union_of(schema) assert object_set is not None components = tuple(object_set.objects(schema)) result = typeref_to_ast(schema, components[0]) for component in components[1:]: result = qlast.TypeOp( left=result, op='|', right=typeref_to_ast(schema, component), ) else: result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef( module=t.get_name(schema).module, name=t.get_name(schema).name)) return result