def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: parsing.ParserContext, ctx: context.ContextLevel) -> irast.Base: 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) 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) 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 type_to_ql_typeref(t: s_types.Type, *, _name=None, schema: s_schema.Schema) -> qlast.TypeName: if t.is_any(): result = qlast.TypeName(name=_name, maintype=qlast.AnyType()) elif t.is_anytuple(): result = qlast.TypeName(name=_name, maintype=qlast.AnyTuple()) elif not isinstance(t, s_abc.Collection): result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef( module=t.get_name(schema).module, name=t.get_name(schema).name)) elif isinstance(t, s_abc.Tuple) and t.named: result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ type_to_ql_typeref(st, _name=sn, schema=schema) for sn, st in t.iter_subtypes(schema) ]) else: result = qlast.TypeName(name=_name, maintype=qlast.ObjectRef(name=t.schema_name), subtypes=[ type_to_ql_typeref(st, schema=schema) for st in t.get_subtypes(schema) ]) return result
def _cast_array_literal( 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(ir_set.expr, irast.Array) orig_typeref = typegen.type_to_typeref(orig_stype, env=ctx.env) new_typeref = typegen.type_to_typeref(new_stype, env=ctx.env) 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) from None assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] intermediate_stype = orig_stype else: el_type = new_stype ctx.env.schema, intermediate_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) intermediate_typeref = typegen.type_to_typeref( intermediate_stype, env=ctx.env) casted_els = [] for el in ir_set.expr.elements: el = compile_cast(el, el_type, cardinality_mod=qlast.CardinalityModifier.Required, ctx=ctx, srcctx=srcctx) casted_els.append(el) new_array = setgen.ensure_set( irast.Array(elements=casted_els, typeref=intermediate_typeref), ctx=ctx) if direct_cast is not None: return _cast_to_ir( new_array, direct_cast, intermediate_stype, new_stype, ctx=ctx) else: cast_ir = irast.TypeCast( expr=new_array, from_type=orig_typeref, to_type=new_typeref, sql_cast=True, sql_expr=False, ) return setgen.ensure_set(cast_ir, ctx=ctx)
def _cast_array_literal(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: parsing.ParserContext, ctx: context.ContextLevel) -> irast.Base: orig_typeref = irtyputils.type_to_typeref(ctx.env.schema, orig_stype) new_typeref = irtyputils.type_to_typeref(ctx.env.schema, new_stype) 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) from None el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype casted_els = [] for el in ir_set.expr.elements: el = compile_cast(el, el_type, ctx=ctx, srcctx=srcctx) casted_els.append(el) new_array = setgen.ensure_set(irast.Array(elements=casted_els, typeref=orig_typeref), ctx=ctx) if direct_cast is not None: return _cast_to_ir(new_array, direct_cast, orig_stype, new_stype, ctx=ctx) else: cast_ir = irast.TypeCast( expr=new_array, from_type=orig_typeref, to_type=new_typeref, sql_cast=True, ) return setgen.ensure_set(cast_ir, ctx=ctx)
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, ), ], ), ], ) 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 compile_cast( ir_expr: Union[irast.Set, irast.Expr], new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel, cardinality_mod: Optional[qlast.CardinalityModifier] = None ) -> irast.Set: if isinstance(ir_expr, irast.EmptySet): # For the common case of casting an empty set, we simply # generate a new EmptySet node of the requested type. return setgen.new_empty_set( stype=new_stype, alias=ir_expr.path_id.target_name_hint.name, ctx=ctx, srcctx=ir_expr.context) elif irutils.is_untyped_empty_array_expr(ir_expr): # Ditto for empty arrays. new_typeref = typegen.type_to_typeref(new_stype, ctx.env) return setgen.ensure_set(irast.Array(elements=[], typeref=new_typeref), ctx=ctx) ir_set = setgen.ensure_set(ir_expr, ctx=ctx) orig_stype = setgen.get_set_type(ir_set, ctx=ctx) if (orig_stype == new_stype and cardinality_mod is not qlast.CardinalityModifier.Required): return ir_set elif orig_stype.is_object_type() and new_stype.is_object_type(): # Object types cannot be cast between themselves, # as cast is a _constructor_ operation, and the only # valid way to construct an object is to INSERT it. raise errors.QueryError( f'cannot cast object type ' f'{orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}, use ' f'`...[IS {new_stype.get_displayname(ctx.env.schema)}]` instead', context=srcctx) if isinstance(ir_set.expr, irast.Array): return _cast_array_literal(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.is_tuple(ctx.env.schema): return _cast_tuple(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.issubclass(ctx.env.schema, new_stype): # The new type is a supertype of the old type, # and is always a wider domain, so we simply reassign # the stype. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, cardinality_mod=cardinality_mod, ctx=ctx) elif new_stype.issubclass(ctx.env.schema, orig_stype): # The new type is a subtype, so may potentially have # a more restrictive domain, generate a cast call. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, cardinality_mod=cardinality_mod, ctx=ctx) elif orig_stype.is_array(): return _cast_array(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) else: json_t = ctx.env.get_track_schema_type('std::json') if (new_stype.issubclass(ctx.env.schema, json_t) and ir_set.path_id.is_objtype_path()): # JSON casts of objects are special: we want the full shape # and not just an identity. with ctx.new() as subctx: subctx.implicit_id_in_shapes = False subctx.implicit_tid_in_shapes = False viewgen.compile_view_shapes(ir_set, ctx=subctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and new_stype.is_enum(ctx.env.schema)): # Casts from json to enums need some special handling # here, where we have access to the enum type. Just turn # it into json->str and str->enum. str_typ = ctx.env.get_track_schema_type('std::str') str_ir = compile_cast(ir_expr, str_typ, srcctx=srcctx, ctx=ctx) return compile_cast(str_ir, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and isinstance(new_stype, s_types.Array) and not new_stype.get_subtypes(ctx.env.schema)[0].issubclass( ctx.env.schema, json_t)): # Turn casts from json->array<T> into json->array<json> # and array<json>->array<T>. ctx.env.schema, json_array_typ = s_types.Array.from_subtypes( ctx.env.schema, [json_t]) json_array_ir = compile_cast(ir_expr, json_array_typ, srcctx=srcctx, ctx=ctx) return compile_cast(json_array_ir, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and isinstance(new_stype, s_types.Tuple)): return _cast_json_to_tuple(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) return _compile_cast(ir_expr, orig_stype, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx)
def type_to_typeref( schema: s_schema.Schema, t: s_types.Type, *, cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None, typename: Optional[s_name.QualName] = None, include_descendants: bool = False, include_ancestors: bool = False, _name: Optional[str] = None, ) -> irast.TypeRef: """Return an instance of :class:`ir.ast.TypeRef` for a given type. An IR TypeRef is an object that fully describes a schema type for the purposes of query compilation. Args: schema: A schema instance, in which the type *t* is defined. t: A schema type instance. cache: Optional mapping from (type UUID, typename) to cached IR TypeRefs. typename: Optional name hint to use for the type in the returned TypeRef. If ``None``, the type name is used. include_descendants: Whether to include the description of all material type descendants of *t*. include_ancestors: Whether to include the description of all material type ancestors of *t*. _name: Optional subtype element name if this type is a collection within a Tuple, Returns: A ``TypeRef`` instance corresponding to the given schema type. """ result: irast.TypeRef material_type: s_types.Type key = (t.id, include_descendants, include_ancestors) if cache is not None and typename is None: cached_result = cache.get(key) if cached_result is not None: # If the schema changed due to an ongoing compilation, the name # hint might be outdated. if cached_result.name_hint == t.get_name(schema): return cached_result if t.is_anytuple(schema): result = irast.AnyTupleRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif t.is_any(schema): result = irast.AnyTypeRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif not isinstance(t, s_types.Collection): assert isinstance(t, s_types.InheritingType) union_of = t.get_union_of(schema) if union_of: non_overlapping, union_is_concrete = ( s_utils.get_non_overlapping_union( schema, union_of.objects(schema), ) ) union = frozenset( type_to_typeref(schema, c, cache=cache) for c in non_overlapping ) else: union_is_concrete = False union = frozenset() intersection_of = t.get_intersection_of(schema) if intersection_of: intersection = frozenset( type_to_typeref(schema, c, cache=cache) for c in intersection_of.objects(schema) ) else: intersection = frozenset() schema, material_type = t.material_type(schema) material_typeref: Optional[irast.TypeRef] if material_type != t: material_typeref = type_to_typeref( schema, material_type, include_descendants=include_descendants, include_ancestors=include_ancestors, cache=cache, ) else: material_typeref = None if (isinstance(material_type, s_scalars.ScalarType) and not material_type.get_abstract(schema)): base_type = material_type.get_topmost_concrete_base(schema) if base_type == material_type: base_typeref = None else: assert isinstance(base_type, s_types.Type) base_typeref = type_to_typeref( schema, base_type, cache=cache ) else: base_typeref = None tname = t.get_name(schema) if typename is not None: name = typename else: name = tname common_parent_ref: Optional[irast.TypeRef] if union_of: common_parent = s_utils.get_class_nearest_common_ancestor( schema, union_of.objects(schema)) assert isinstance(common_parent, s_types.Type) common_parent_ref = type_to_typeref( schema, common_parent, cache=cache ) else: common_parent_ref = None descendants: Optional[FrozenSet[irast.TypeRef]] if material_typeref is None and include_descendants: descendants = frozenset( type_to_typeref( schema, child, cache=cache, include_descendants=True, include_ancestors=include_ancestors, ) for child in t.children(schema) if not child.get_is_derived(schema) ) else: descendants = None ancestors: Optional[FrozenSet[irast.TypeRef]] if material_typeref is None and include_ancestors: ancestors = frozenset( type_to_typeref( schema, ancestor, cache=cache, include_descendants=include_descendants, include_ancestors=False ) for ancestor in t.get_ancestors(schema).objects(schema) ) else: ancestors = None result = irast.TypeRef( id=t.id, name_hint=name, material_type=material_typeref, base_type=base_typeref, descendants=descendants, ancestors=ancestors, union=union, union_is_concrete=union_is_concrete, intersection=intersection, common_parent=common_parent_ref, element_name=_name, is_scalar=t.is_scalar(), is_abstract=t.get_abstract(schema), is_view=t.is_view(schema), is_opaque_union=t.get_is_opaque_union(schema), ) elif isinstance(t, s_types.Tuple) and t.is_named(schema): schema, material_type = t.material_type(schema) if material_type != t: material_typeref = type_to_typeref( schema, material_type, cache=cache ) else: material_typeref = None result = irast.TypeRef( id=t.id, name_hint=typename or t.get_name(schema), material_type=material_typeref, element_name=_name, collection=t.schema_name, in_schema=t.get_is_persistent(schema), subtypes=tuple( type_to_typeref(schema, st, _name=sn) # note: no cache for sn, st in t.iter_subtypes(schema) ) ) else: schema, material_type = t.material_type(schema) if material_type != t: material_typeref = type_to_typeref( schema, material_type, cache=cache ) else: material_typeref = None result = irast.TypeRef( id=t.id, name_hint=typename or t.get_name(schema), material_type=material_typeref, element_name=_name, collection=t.schema_name, in_schema=t.get_is_persistent(schema), subtypes=tuple( type_to_typeref(schema, st, cache=cache) for st in t.get_subtypes(schema) ) ) if cache is not None and typename is None and _name is None: # Note: there is no cache for `_name` variants since they are only used # for Tuple subtypes and thus they will be cached on the outer level # anyway. # There's also no variant for types with custom typenames since they # proved to have a very low hit rate. # This way we save on the size of the key tuple. cache[key] = result return result
def type_to_typeref( schema: s_schema.Schema, t: s_types.Type, *, cache: Optional[Dict[uuid.UUID, irast.TypeRef]] = None, typename: Optional[s_name.Name] = None, _name: Optional[str] = None, ) -> irast.TypeRef: """Return an instance of :class:`ir.ast.TypeRef` for a given type. An IR TypeRef is an object that fully describes a schema type for the purposes of query compilation. Args: schema: A schema instance, in which the type *t* is defined. t: A schema type instance. cache: Optional mapping from (type UUID, typename) to cached IR TypeRefs. typename: Optional name hint to use for the type in the returned TypeRef. If ``None``, the type name is used. _name: Optional subtype element name if this type is a collection within a Tuple, Returns: A ``TypeRef`` instance corresponding to the given schema type. """ result: irast.TypeRef if cache is not None and typename is None: cached_result = cache.get(t.id) if cached_result is not None: # If the schema changed due to an ongoing compilation, the name # hint might be outdated. if cached_result.name_hint == t.get_name(schema): return cached_result if t.is_anytuple(): result = irast.AnyTupleRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif t.is_any(): result = irast.AnyTypeRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif not isinstance(t, s_abc.Collection): union_of = t.get_union_of(schema) if union_of: non_overlapping, union_is_concrete = ( s_utils.get_non_overlapping_union( schema, union_of.objects(schema), )) union = frozenset( type_to_typeref(schema, c, cache=cache) for c in non_overlapping) else: union_is_concrete = False union = frozenset() intersection_of = t.get_intersection_of(schema) if intersection_of: intersection = frozenset( type_to_typeref(schema, c, cache=cache) for c in intersection_of.objects(schema)) else: intersection = frozenset() material_type = t.material_type(schema) material_typeref: Optional[irast.TypeRef] if material_type is not t: material_typeref = type_to_typeref(schema, material_type, cache=cache) else: material_typeref = None if (isinstance(material_type, s_scalars.ScalarType) and not material_type.get_is_abstract(schema)): base_type = material_type.get_topmost_concrete_base(schema) if base_type is material_type: base_typeref = None else: assert isinstance(base_type, s_types.Type) base_typeref = type_to_typeref(schema, base_type, cache=cache) else: base_typeref = None if typename is not None: name = typename else: name = t.get_name(schema) module = schema.get_global(s_mod.Module, name.module) common_parent_ref: Optional[irast.TypeRef] if union_of: common_parent = s_utils.get_class_nearest_common_ancestor( schema, union_of.objects(schema)) assert isinstance(common_parent, s_types.Type) common_parent_ref = type_to_typeref(schema, common_parent, cache=cache) else: common_parent_ref = None result = irast.TypeRef( id=t.id, module_id=module.id, name_hint=name, material_type=material_typeref, base_type=base_typeref, union=union, union_is_concrete=union_is_concrete, intersection=intersection, common_parent=common_parent_ref, element_name=_name, is_scalar=t.is_scalar(), is_abstract=t.get_is_abstract(schema), is_view=t.is_view(schema), is_opaque_union=t.get_is_opaque_union(schema), ) elif isinstance(t, s_abc.Tuple) and t.named: result = irast.TypeRef( id=t.id, name_hint=typename or t.get_name(schema), element_name=_name, collection=t.schema_name, in_schema=schema.get_by_id(t.id, None) is not None, subtypes=tuple( type_to_typeref(schema, st, _name=sn) # note: no cache for sn, st in t.iter_subtypes(schema))) else: result = irast.TypeRef(id=t.id, name_hint=typename or t.get_name(schema), element_name=_name, collection=t.schema_name, in_schema=schema.get_by_id(t.id, None) is not None, subtypes=tuple( type_to_typeref(schema, st, cache=cache) for st in t.get_subtypes(schema))) if cache is not None and typename is None and _name is None: # Note: there is no cache for `_name` variants since they are only used # for Tuple subtypes and thus they will be cached on the outer level # anyway. # There's also no variant for types with custom typenames since they # proved to have a very low hit rate. # This way we save on the size of the key tuple. cache[t.id] = result return result
def type_to_typeref(schema, t: s_types.Type, *, _name=None, typename=None) -> irast.TypeRef: if t.is_anytuple(): result = irast.AnyTupleRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif t.is_any(): result = irast.AnyTypeRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif not isinstance(t, s_abc.Collection): union_of = t.get_union_of(schema) if union_of: children = frozenset( type_to_typeref(schema, c) for c in union_of.objects(schema)) else: children = frozenset() material_type = t.material_type(schema) if material_type is not t: material_typeref = type_to_typeref(schema, material_type) else: material_typeref = None if (material_type.is_scalar() and not material_type.get_is_abstract(schema)): base_type = material_type.get_topmost_concrete_base(schema) if base_type is material_type: base_typeref = None else: base_typeref = type_to_typeref(schema, base_type) else: base_typeref = None if typename is not None: name = typename else: name = t.get_name(schema) module = schema.get_global(s_mod.Module, name.module) if union_of: common_parent = s_utils.get_class_nearest_common_ancestor( schema, union_of.objects(schema)) common_parent_ref = type_to_typeref(schema, common_parent) else: common_parent_ref = None result = irast.TypeRef( id=t.id, module_id=module.id, name_hint=name, material_type=material_typeref, base_type=base_typeref, children=children, common_parent=common_parent_ref, element_name=_name, is_scalar=t.is_scalar(), is_abstract=t.get_is_abstract(schema), is_view=t.is_view(schema), is_opaque_union=t.get_is_opaque_union(schema), ) elif isinstance(t, s_abc.Tuple) and t.named: result = irast.TypeRef(id=t.id, name_hint=typename or t.get_name(schema), element_name=_name, collection=t.schema_name, in_schema=schema.get_by_id(t.id, None) is not None, subtypes=tuple( type_to_typeref(schema, st, _name=sn) for sn, st in t.iter_subtypes(schema))) else: result = irast.TypeRef(id=t.id, name_hint=typename or t.get_name(schema), element_name=_name, collection=t.schema_name, in_schema=schema.get_by_id(t.id, None) is not None, subtypes=tuple( type_to_typeref(schema, st) for st in t.get_subtypes(schema))) return result
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: with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_path = subctx.create_anchor(ir_set, 'a') unpacked = qlast.FunctionCall( func=('__std__', 'array_unpack'), args=[source_path], ) enumerated = dispatch.compile( qlast.FunctionCall( func=('__std__', 'enumerate'), args=[unpacked], ), ctx=subctx, ) enumerated_ref = subctx.create_anchor(enumerated, 'e') elements = qlast.FunctionCall( func=('__std__', 'array_agg'), args=[ qlast.SelectQuery( result=qlast.TypeCast( expr=astutils.extend_path(enumerated_ref, '1'), type=typegen.type_to_ql_typeref( el_type, ctx=subctx, ), cardinality_mod=qlast.CardinalityModifier.Required, ), orderby=[ qlast.SortExpr( path=astutils.extend_path(enumerated_ref, '0'), direction=qlast.SortOrder.Asc, ), ], ), ], ) # Force the elements to be correlated with whatever the # anchor was. (Doing it this way ensures a NULL check, # and just registering it in the scope would not.) correlated_elements = astutils.extend_path( qlast.Tuple(elements=[source_path, elements]), '1' ) if el_type.contains_json(subctx.env.schema): subctx.inhibit_implicit_limit = True array_ir = dispatch.compile(correlated_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