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 new_set( *, stype: s_types.Type, ctx: context.ContextLevel, ircls: Type[irast.Set] = irast.Set, **kwargs: Any, ) -> irast.Set: """Create a new ir.Set instance with given attributes. Absolutely all ir.Set instances must be created using this constructor. """ if (stype not in ctx.type_rewrites and isinstance(stype, s_objtypes.ObjectType) and ctx.env.options.apply_query_rewrites and (filters := stype.get_access_policy_filters(ctx.env.schema))): qry = qlast.SelectQuery(result=qlast.Path( steps=[s_utils.name_to_ast_ref(stype.get_name(ctx.env.schema))]), ) for f in filters: assert isinstance(f.qlast, qlast.Expr) qry.where = astutils.extend_binop(qry.where, f.qlast) with ctx.detached() as subctx: subctx.expr_exposed = False # This is a global rewrite operation that is done once # per type, and so we don't really care if we're in a # temporary scope or not. subctx.path_scope = subctx.env.path_scope.root subctx.in_temp_scope = False # Put a placeholder to prevent recursion. subctx.type_rewrites[stype] = irast.Set() filtered_set = dispatch.compile(qry, ctx=subctx) assert isinstance(filtered_set, irast.Set) subctx.type_rewrites[stype] = filtered_set
def declare_view_from_schema(viewcls: s_types.Type, *, ctx: context.ContextLevel) -> s_types.Type: vc = ctx.env.schema_view_cache.get(viewcls) if vc is not None: return vc with ctx.detached() as subctx: subctx.expr_exposed = False view_expr = viewcls.get_expr(ctx.env.schema) assert view_expr is not None view_ql = qlparser.parse(view_expr.text) viewcls_name = viewcls.get_name(ctx.env.schema) assert isinstance(view_ql, qlast.Expr), 'expected qlast.Expr' view_set = declare_view(view_ql, alias=viewcls_name, fully_detached=True, 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) vc = subctx.aliased_views[viewcls_name] assert vc is not None ctx.env.schema_view_cache[viewcls] = vc ctx.source_map.update(subctx.source_map) ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name] ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc ctx.view_sets[vc] = subctx.view_sets[vc] return vc
def declare_view_from_schema(viewcls: s_types.Type, *, ctx: context.ContextLevel) -> s_types.Type: vc = ctx.env.schema_view_cache.get(viewcls) if vc is not None: return vc with ctx.detached() as subctx: subctx.expr_exposed = False view_expr = viewcls.get_expr(ctx.env.schema) assert view_expr is not None view_ql = qlparser.parse(view_expr.text) viewcls_name = viewcls.get_name(ctx.env.schema) assert isinstance(view_ql, qlast.Expr), 'expected qlast.Expr' view_set = declare_view(view_ql, alias=viewcls_name, fully_detached=True, 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) vc = subctx.aliased_views[viewcls_name] assert vc is not None if not ctx.in_temp_scope: ctx.env.schema_view_cache[viewcls] = vc ctx.source_map.update(subctx.source_map) ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name] ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc ctx.view_sets[vc] = subctx.view_sets[vc] # XXX: The current cardinality inference machine does not look # into unreferenced expression parts, which includes computables # that may be declared on an alias that another alias is referencing, # leaving Unknown cardinalities in place. To fix this, copy # cardinalities for computed pointers from the alias object in the # schema. view_type = setgen.get_set_type(view_set, ctx=subctx) if isinstance(view_type, s_objtypes.ObjectType): assert isinstance(viewcls, s_objtypes.ObjectType) _fixup_cardinalities( view_type, viewcls, ctx=ctx, ) return vc
def object_type_to_python_type( objtype: s_types.Type, schema: s_schema.Schema, *, base_class: typing.Optional[type] = None, _memo: typing.Optional[typing.Mapping[s_types.Type, type]] = None) -> type: if _memo is None: _memo = {} fields = [] subclasses = [] for pn, p in objtype.get_pointers(schema).items(schema): if pn in ('id', '__type__'): continue ptype = p.get_target(schema) if ptype.is_object_type(): pytype = _memo.get(ptype) if pytype is None: pytype = object_type_to_python_type(ptype, schema, base_class=base_class, _memo=_memo) _memo[ptype] = pytype for subtype in ptype.children(schema): subclasses.append( object_type_to_python_type(subtype, schema, base_class=pytype, _memo=_memo)) else: pytype = scalar_type_to_python_type(ptype, schema) is_multi = p.get_cardinality(schema) is qltypes.Cardinality.MANY if is_multi: pytype = typing.FrozenSet[pytype] default = p.get_default(schema) if default is None: if p.get_required(schema): default = dataclasses.MISSING else: default = ql_compiler.evaluate_to_python_val(default.text, schema=schema) if is_multi and not isinstance(default, frozenset): default = frozenset((default, )) constraints = p.get_constraints(schema).objects(schema) exclusive = schema.get('std::exclusive') unique = (not ptype.is_object_type() and any( c.issubclass(schema, exclusive) for c in constraints)) field = dataclasses.field( compare=unique, hash=unique, repr=True, default=default, ) fields.append((pn, pytype, field)) return dataclasses.make_dataclass( objtype.get_name(schema).name, fields=fields, bases=(base_class, ) if base_class is not None else (), frozen=True, namespace={'_subclasses': subclasses}, )
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 derive_view( stype: s_types.Type, *, derived_name: typing.Optional[sn.SchemaName]=None, derived_name_quals: typing.Optional[typing.Sequence[str]]=(), derived_name_base: typing.Optional[str]=None, preserve_shape: bool=False, preserve_path_id: bool=False, is_insert: bool=False, is_update: bool=False, inheritance_merge: bool=True, attrs: typing.Optional[dict]=None, ctx: context.ContextLevel) -> s_types.Type: if derived_name is None: derived_name = derive_view_name( stype=stype, derived_name_quals=derived_name_quals, derived_name_base=derived_name_base, ctx=ctx) if is_insert: vtype = s_types.ViewType.Insert elif is_update: vtype = s_types.ViewType.Update else: vtype = s_types.ViewType.Select if attrs is None: attrs = {} else: attrs = dict(attrs) attrs['view_type'] = vtype derived: s_types.Type if isinstance(stype, s_abc.Collection): ctx.env.schema, derived = stype.derive_subtype( ctx.env.schema, name=derived_name) elif isinstance(stype, s_inh.InheritingObject): qualifiers: typing.Tuple[str, ...] = () if stype.get_name(ctx.env.schema) == derived_name: qualifiers = (ctx.aliases.get('d'),) ctx.env.schema, derived = stype.derive( ctx.env.schema, stype, *qualifiers, name=derived_name, inheritance_merge=inheritance_merge, refdict_whitelist={'pointers'}, mark_derived=True, preserve_path_id=preserve_path_id, attrs=attrs) if (not stype.generic(ctx.env.schema) and isinstance(derived, s_sources.Source)): scls_pointers = stype.get_pointers(ctx.env.schema) derived_own_pointers = derived.get_pointers(ctx.env.schema) for pn, ptr in derived_own_pointers.items(ctx.env.schema): # This is a view of a view. Make sure query-level # computable expressions for pointers are carried over. src_ptr = scls_pointers.get(ctx.env.schema, pn) computable_data = ctx.source_map.get(src_ptr) if computable_data is not None: ctx.source_map[ptr] = computable_data ctx.view_nodes[derived.get_name(ctx.env.schema)] = derived if preserve_shape and stype in ctx.env.view_shapes: ctx.env.view_shapes[derived] = ctx.env.view_shapes[stype] return derived
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