def compile_TypeName( node: qlast.TypeName, *, schema: s_schema.Schema, modaliases: Mapping[Optional[str], str], localnames: AbstractSet[str] = frozenset(), ) -> None: # Resolve the main type if isinstance(node.maintype, qlast.ObjectRef): # This is a specific path root, resolve it. if (not node.maintype.module and # maintype names 'array' and 'tuple' specifically # should also be ignored node.maintype.name not in {'array', 'tuple', *localnames}): maintype = schema.get( node.maintype.name, default=None, module_aliases=modaliases, ) if maintype is not None: node.maintype.module = maintype.get_name(schema).module elif None in modaliases: # Even if the name was not resolved in the schema it # may be the name of the object being defined, as such # the default module should be used. Names that must # be ignored (like aliases and parameters) have # already been filtered by the localnames. node.maintype.module = modaliases[None] if node.subtypes is not None: for st in node.subtypes: normalize( st, schema=schema, modaliases=modaliases, localnames=localnames, )
def scalar_type_to_python_type(stype: s_types.Type, schema: s_schema.Schema) -> type: typemap = { 'std::str': str, 'std::anyint': int, 'std::anyfloat': float, 'std::decimal': decimal.Decimal, 'std::bigint': decimal.Decimal, 'std::bool': bool, 'std::json': str, 'std::uuid': uuidgen.UUID, } for basetype_name, python_type in typemap.items(): basetype = schema.get(basetype_name) assert isinstance(basetype, s_inh.InheritingObject) if stype.issubclass(schema, basetype): return python_type raise UnsupportedExpressionError( f'{stype.get_displayname(schema)} is not representable in Python')
def compile_FunctionCall( node: qlast.FunctionCall, *, schema: s_schema.Schema, modaliases: Mapping[Optional[str], str], localnames: AbstractSet[str] = frozenset(), ) -> None: if isinstance(node.func, str) and node.func not in localnames: funcs = schema.get_functions(node.func, default=tuple(), module_aliases=modaliases) if funcs: # As long as we found some functions, they will be from # the same module (the first valid resolved module for the # function name will mask "std"). _, fname = funcs[0].get_name(schema).as_tuple() _, module, name = sn.split_name(sn.shortname_from_fullname(fname)) node.func = (module, name) # It's odd we don't find a function, but this will be picked up # by the compiler with a more appropriate error message. for arg in node.args: normalize( arg, schema=schema, modaliases=modaliases, localnames=localnames, ) for val in node.kwargs.values(): normalize( val, schema=schema, modaliases=modaliases, localnames=localnames, )
def is_castable( schema: s_schema.Schema, source: s_types.Type, target: s_types.Type, ) -> bool: # Implicitly castable if is_implicitly_castable(schema, source, target): return True elif is_assignment_castable(schema, source, target): return True else: casts = schema.get_casts_to_type(target) if not casts: return False else: for c in casts: if c.get_from_type(schema) == source: return True else: return False
def format_error_message( self, schema: s_schema.Schema, subjtitle: str, ) -> str: errmsg = self.get_errmessage(schema) args = self.get_args(schema) if args: args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.ObjectRef(name=subjtitle)]), ] args_ql.extend(arg.qlast for arg in args) constr_base: Constraint = schema.get( self.get_name(schema), type=type(self)) index_parameters = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) expr = constr_base.get_field_value(schema, 'expr') expr_ql = qlparser.parse(expr.text) qlutils.inline_parameters(expr_ql, index_parameters) args_map = {name: edgeql.generate_source(val, pretty=False) for name, val in index_parameters.items()} else: args_map = {'__subject__': subjtitle} assert errmsg is not None formatted = errmsg.format(**args_map) return formatted
def _is_reachable( schema: s_schema.Schema, cast_kwargs: Mapping[str, bool], source: s_types.Type, target: s_types.Type, distance: int, ) -> int: if source == target: return distance casts = schema.get_casts_to_type(target, **cast_kwargs) if not casts: return _NOT_REACHABLE sources = {c.get_from_type(schema) for c in casts} distance += 1 if source in sources: return distance else: return min( _is_reachable(schema, cast_kwargs, source, s, distance) for s in sources)
def compile_Path( node: qlast.Path, *, schema: s_schema.Schema, modaliases: Mapping[Optional[str], str], localnames: AbstractSet[str] = frozenset(), ) -> None: for step in node.steps: if isinstance(step, (qlast.Expr, qlast.TypeIntersection)): normalize( step, schema=schema, modaliases=modaliases, localnames=localnames, ) elif isinstance(step, qlast.ObjectRef): # This is a specific path root, resolve it. if not step.module and step.name not in localnames: obj = schema.get( step.name, default=None, module_aliases=modaliases, ) if obj is not None: name = obj.get_name(schema) assert isinstance(name, sn.QualName) step.module = name.module elif None in modaliases: # Even if the name was not resolved in the # schema it may be the name of the object # being defined, as such the default module # should be used. Names that must be ignored # (like aliases and parameters) have already # been filtered by the localnames. step.module = modaliases[None]
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> CreateConstraint: cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteConstraint): if astnode.delegated: cmd.set_attribute_value('delegated', astnode.delegated) args = cls._constraint_args_from_ast(schema, astnode, context) if args: cmd.set_attribute_value('args', args) elif isinstance(astnode, qlast.CreateConstraint): params = cls._get_param_desc_from_ast(schema, context.modaliases, astnode) for param in params: if param.get_kind(schema) is ft.ParameterKind.NAMED_ONLY: raise errors.InvalidConstraintDefinitionError( 'named only parameters are not allowed ' 'in this context', context=astnode.context) if param.get_default(schema) is not None: raise errors.InvalidConstraintDefinitionError( 'constraints do not support parameters ' 'with defaults', context=astnode.context) if cmd.get_attribute_value('return_type') is None: cmd.set_attribute_value( 'return_type', schema.get('std::bool'), ) if cmd.get_attribute_value('return_typemod') is None: cmd.set_attribute_value( 'return_typemod', ft.TypeModifier.SINGLETON, ) assert isinstance( astnode, (qlast.CreateConstraint, qlast.CreateConcreteConstraint)) # 'subjectexpr' can be present in either astnode type if astnode.subjectexpr: orig_text = cls.get_orig_expr_text(schema, astnode, 'subjectexpr') subjectexpr = s_expr.Expression.from_ast( astnode.subjectexpr, schema, context.modaliases, orig_text=orig_text, ) cmd.set_attribute_value( 'subjectexpr', subjectexpr, ) cls._validate_subcommands(astnode) assert isinstance(cmd, CreateConstraint) return cmd
def parse_into( schema: s_schema.Schema, data: Sequence[str], schema_class_layout: Dict[Type[s_obj.Object], sr_struct.SchemaTypeLayout], ) -> s_schema.Schema: """Parse JSON-encoded schema objects and populate the schema with them. Args: schema: A schema instance to use as a starting point. data: A sequence of JSON-encoded schema object data as returned by an introspection query. schema_class_layout: A mapping describing schema class layout in the reflection, as returned from :func:`schema.reflection.structure.generate_structure`. Returns: A schema instance including objects encoded in the provided JSON sequence. """ id_to_type = {} id_to_data = {} name_to_id = {} shortname_to_id = collections.defaultdict(set) globalname_to_id = {} dict_of_dicts: Callable[ [], Dict[Tuple[Type[s_obj.Object], str], Dict[uuid.UUID, None]], ] = functools.partial(collections.defaultdict, dict) # type: ignore refs_to: Dict[ uuid.UUID, Dict[Tuple[Type[s_obj.Object], str], Dict[uuid.UUID, None]] ] = collections.defaultdict(dict_of_dicts) objects: Dict[uuid.UUID, Tuple[s_obj.Object, Dict[str, Any]]] = {} for entry_json in data: entry = json.loads(entry_json) _, _, clsname = entry['_tname'].rpartition('::') mcls = s_obj.ObjectMeta.get_schema_metaclass(clsname) if mcls is None: raise ValueError( f'unexpected type in schema reflection: {clsname}') objid = uuidgen.UUID(entry['id']) objects[objid] = (mcls._create_from_id(objid), entry) refdict_updates = {} for objid, (obj, entry) in objects.items(): mcls = type(obj) name = entry['name__internal'] layout = schema_class_layout[mcls] if isinstance(obj, s_obj.QualifiedObject): name = s_name.Name(name) name_to_id[name] = objid else: globalname_to_id[mcls, name] = objid if isinstance(obj, (s_func.Function, s_oper.Operator)): shortname = mcls.get_shortname_static(name) shortname_to_id[mcls, shortname].add(objid) id_to_type[objid] = obj objdata: Dict[str, Any] = {} val: Any for k, v in entry.items(): desc = layout.get(k) if desc is None: continue fn = desc.fieldname if desc.storage is not None: if v is None: pass elif desc.storage.ptrkind == 'link': refid = uuidgen.UUID(v['id']) newobj = objects.get(refid) if newobj is not None: val = newobj[0] else: val = schema.get_by_id(refid) objdata[fn] = val refs_to[val.id][mcls, fn][objid] = None elif desc.storage.ptrkind == 'multi link': ftype = mcls.get_field(fn).type if issubclass(ftype, s_obj.ObjectDict): refids = ftype._container( uuidgen.UUID(e['value']) for e in v) val = ftype(refids, _private_init=True) val._keys = tuple(e['name'] for e in v) else: refids = ftype._container( uuidgen.UUID(e['id']) for e in v) val = ftype(refids, _private_init=True) objdata[fn] = val for refid in refids: refs_to[refid][mcls, fn][objid] = None elif desc.storage.shadow_ptrkind: val = entry[f'{k}__internal'] ftype = mcls.get_field(fn).type if val is not None and type(val) is not ftype: if issubclass(ftype, s_expr.Expression): val = _parse_expression(val) for refid in val.refs.ids(schema): refs_to[refid][mcls, fn][objid] = None elif issubclass(ftype, s_expr.ExpressionList): exprs = [] for e_dict in val: e = _parse_expression(e_dict) assert e.refs is not None for refid in e.refs.ids(schema): refs_to[refid][mcls, fn][objid] = None exprs.append(e) val = ftype(exprs) else: val = ftype(val) objdata[fn] = val else: ftype = mcls.get_field(fn).type if type(v) is not ftype: objdata[fn] = ftype(v) else: objdata[fn] = v elif desc.is_refdict: ftype = mcls.get_field(fn).type refids = ftype._container(uuidgen.UUID(e['id']) for e in v) for refid in refids: refs_to[refid][mcls, fn][objid] = None val = ftype(refids, _private_init=True) objdata[fn] = val if desc.properties: for e_dict in v: refdict_updates[uuidgen.UUID(e_dict['id'])] = { p: pv for p in desc.properties if (pv := e_dict[f'@{p}']) is not None } id_to_data[objid] = immutables.Map(objdata) for objid, updates in refdict_updates.items(): if updates: id_to_data[objid] = id_to_data[objid].update(updates) with schema._refs_to.mutate() as mm: for referred_id, refdata in refs_to.items(): try: refs = mm[referred_id] except KeyError: refs = immutables.Map(( (k, immutables.Map(r)) for k, r in refdata.items() )) else: refs_update = {} for k, referrers in refdata.items(): try: rt = refs[k] except KeyError: rt = immutables.Map(referrers) else: rt = rt.update(referrers) refs_update[k] = rt refs = refs.update(refs_update) mm[referred_id] = refs schema = schema._replace( id_to_type=schema._id_to_type.update(id_to_type), id_to_data=schema._id_to_data.update(id_to_data), name_to_id=schema._name_to_id.update(name_to_id), shortname_to_id=schema._shortname_to_id.update( (k, frozenset(v)) for k, v in shortname_to_id.items() ), globalname_to_id=schema._globalname_to_id.update(globalname_to_id), refs_to=mm.finish(), ) return schema
def _populate_concrete_constraint_attrs( self, schema: s_schema.Schema, subject_obj: Optional[so.Object], *, name: str, subjectexpr: Optional[s_expr.Expression] = None, sourcectx: Optional[c_parsing.ParserContext] = None, args: Any = None, **kwargs: Any) -> None: from edb.ir import ast as ir_ast from edb.ir import utils as ir_utils constr_base = schema.get(name, type=Constraint) orig_subjectexpr = subjectexpr orig_subject = subject_obj base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( f'subjectexpr is already defined for {name!r}') if (isinstance(subject_obj, s_scalars.ScalarType) and constr_base.get_is_aggregate(schema)): raise errors.InvalidConstraintDefinitionError( f'{constr_base.get_verbosename(schema)} may not ' f'be used on scalar types') if subjectexpr is not None: subject_ql = subjectexpr.qlast subject = subject_ql else: subject = subject_obj expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name!r}') # Re-parse instead of using expr.qlast, because we mutate # the AST below. expr_ql = qlparser.parse(expr.text) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) inherited = dict() if orig_subjectexpr is not None: attrs['subjectexpr'] = orig_subjectexpr else: base_subjectexpr = constr_base.get_subjectexpr(schema) if base_subjectexpr is not None: attrs['subjectexpr'] = base_subjectexpr inherited['subjectexpr'] = True errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) inherited['errmessage'] = True attrs['errmessage'] = errmessage if subject is not orig_subject: # subject has been redefined assert isinstance(subject, qlast.Base) qlutils.inline_anchors(expr_ql, anchors={qlast.Subject().name: subject}) subject = orig_subject if args: args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.Subject()]), ] args_ql.extend(arg.qlast for arg in args) args_map = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) qlutils.inline_parameters(expr_ql, args_map) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None assert subject is not None final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, {}), schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, ), ) bool_t: s_scalars.ScalarType = schema.get('std::bool') assert isinstance(final_expr.irast, ir_ast.Statement) expr_type = final_expr.irast.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_verbosename(schema)}', context=expr_context) if (subjectexpr is not None and isinstance(subject_obj, s_types.Type) and subject_obj.is_object_type()): final_subjectexpr = s_expr.Expression.compiled( subjectexpr, schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, singletons=frozenset({subject_obj}), ), ) assert isinstance(final_subjectexpr.irast, ir_ast.Statement) if final_subjectexpr.irast.cardinality.is_multi(): refs = ir_utils.get_longest_paths(final_expr.irast) if len(refs) > 1: raise errors.InvalidConstraintDefinitionError( "Constraint with multi cardinality may not " "reference multiple links or properties", context=expr_context) attrs['return_type'] = constr_base.get_return_type(schema) attrs['return_typemod'] = constr_base.get_return_typemod(schema) attrs['finalexpr'] = final_expr attrs['params'] = constr_base.get_params(schema) attrs['is_abstract'] = False for k, v in attrs.items(): self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
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 update_schema(self, new_schema: s_schema.Schema): assert isinstance(new_schema, s_schema.ChainedSchema) self._current = self._current._replace( user_schema=new_schema.get_top_schema(), global_schema=new_schema.get_global_schema(), )
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> CreateConstraint: cmd = super()._cmd_tree_from_ast(schema, astnode, context) if isinstance(astnode, qlast.CreateConcreteConstraint): if astnode.delegated: cmd.set_attribute_value('delegated', astnode.delegated) args = cls._constraint_args_from_ast(schema, astnode, context) if args: cmd.set_attribute_value('args', args) elif isinstance(astnode, qlast.CreateConstraint): params = cls._get_param_desc_from_ast(schema, context.modaliases, astnode) for param in params: if param.get_kind(schema) is ft.ParameterKind.NamedOnlyParam: raise errors.InvalidConstraintDefinitionError( 'named only parameters are not allowed ' 'in this context', context=astnode.context) if param.get_default(schema) is not None: raise errors.InvalidConstraintDefinitionError( 'constraints do not support parameters ' 'with defaults', context=astnode.context) if cmd.get_attribute_value('return_type') is None: cmd.set_attribute_value( 'return_type', schema.get('std::bool'), ) if cmd.get_attribute_value('return_typemod') is None: cmd.set_attribute_value( 'return_typemod', ft.TypeModifier.SingletonType, ) assert isinstance( astnode, (qlast.CreateConstraint, qlast.CreateConcreteConstraint)) # 'subjectexpr' can be present in either astnode type if astnode.subjectexpr: orig_text = cls.get_orig_expr_text(schema, astnode, 'subjectexpr') if (orig_text is not None and context.compat_ver_is_before( (1, 0, verutils.VersionStage.ALPHA, 6))): # Versions prior to a6 used a different expression # normalization strategy, so we must renormalize the # expression. expr_ql = qlcompiler.renormalize_compat( astnode.subjectexpr, orig_text, schema=schema, localnames=context.localnames, ) else: expr_ql = astnode.subjectexpr subjectexpr = s_expr.Expression.from_ast( expr_ql, schema, context.modaliases, context.localnames, ) cmd.set_attribute_value( 'subjectexpr', subjectexpr, ) cls._validate_subcommands(astnode) assert isinstance(cmd, CreateConstraint) return cmd
def _populate_concrete_constraint_attrs( self, schema: s_schema.Schema, context: sd.CommandContext, subject_obj: Optional[so.Object], *, name: sn.QualName, subjectexpr: Optional[s_expr.Expression] = None, sourcectx: Optional[c_parsing.ParserContext] = None, args: Any = None, **kwargs: Any) -> None: from edb.ir import ast as ir_ast from edb.ir import utils as ir_utils constr_base = schema.get(name, type=Constraint) orig_subjectexpr = subjectexpr orig_subject = subject_obj base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( f'subjectexpr is already defined for {name}') if (isinstance(subject_obj, s_scalars.ScalarType) and constr_base.get_is_aggregate(schema)): raise errors.InvalidConstraintDefinitionError( f'{constr_base.get_verbosename(schema)} may not ' f'be used on scalar types') if subjectexpr is not None: subject_ql = subjectexpr.qlast subject = subject_ql else: subject = subject_obj expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name}') # Re-parse instead of using expr.qlast, because we mutate # the AST below. expr_ql = qlparser.parse(expr.text) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) inherited = dict() if orig_subjectexpr is not None: attrs['subjectexpr'] = orig_subjectexpr else: base_subjectexpr = constr_base.get_subjectexpr(schema) if base_subjectexpr is not None: attrs['subjectexpr'] = base_subjectexpr inherited['subjectexpr'] = True errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) inherited['errmessage'] = True attrs['errmessage'] = errmessage if subject is not orig_subject: # subject has been redefined assert isinstance(subject, qlast.Base) qlutils.inline_anchors(expr_ql, anchors={qlast.Subject().name: subject}) subject = orig_subject if args: args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.Subject()]), ] args_ql.extend(arg.qlast for arg in args) args_map = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) qlutils.inline_parameters(expr_ql, args_map) attrs['args'] = args assert subject is not None path_prefix_anchor = (qlast.Subject().name if isinstance( subject, s_types.Type) else None) final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, {}), schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, apply_query_rewrites=not context.stdmode, ), ) bool_t = schema.get('std::bool', type=s_scalars.ScalarType) assert isinstance(final_expr.irast, ir_ast.Statement) expr_type = final_expr.irast.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_verbosename(schema)}', context=sourcectx) if subjectexpr is not None: if (isinstance(subject_obj, s_types.Type) and subject_obj.is_object_type()): singletons = frozenset({subject_obj}) else: singletons = frozenset() final_subjectexpr = s_expr.Expression.compiled( subjectexpr, schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, singletons=singletons, apply_query_rewrites=not context.stdmode, ), ) assert isinstance(final_subjectexpr.irast, ir_ast.Statement) refs = ir_utils.get_longest_paths(final_expr.irast) has_multi = False for ref in refs: while ref.rptr: rptr = ref.rptr if rptr.ptrref.dir_cardinality.is_multi(): has_multi = True if (not isinstance(rptr.ptrref, ir_ast.TupleIndirectionPointerRef) and rptr.ptrref.source_ptr is None and rptr.source.rptr is not None): raise errors.InvalidConstraintDefinitionError( "constraints cannot contain paths with more " "than one hop", context=sourcectx) ref = rptr.source if has_multi and len(refs) > 1: raise errors.InvalidConstraintDefinitionError( "cannot reference multiple links or properties in a " "constraint where at least one link or property is MULTI", context=sourcectx) if has_multi and ir_utils.contains_set_of_op( final_subjectexpr.irast): raise errors.InvalidConstraintDefinitionError( "cannot use aggregate functions or operators " "in a non-aggregating constraint", context=sourcectx) attrs['return_type'] = constr_base.get_return_type(schema) attrs['return_typemod'] = constr_base.get_return_typemod(schema) attrs['finalexpr'] = final_expr attrs['params'] = constr_base.get_params(schema) attrs['abstract'] = False for k, v in attrs.items(): self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
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), ) ctx.objects[s_name.QualName('std', 'anytype')] = (schema.get_global( s_pseudo.PseudoType, 'anytype')) ctx.objects[s_name.QualName('std', 'anytuple')] = (schema.get_global( s_pseudo.PseudoType, 'anytuple')) 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): ctx.objects[fq_name] = qltracer.ObjectType(fq_name) elif isinstance(decl_ast, qlast.CreateAlias): ctx.objects[fq_name] = qltracer.Alias(fq_name) elif isinstance(decl_ast, qlast.CreateScalarType): ctx.objects[fq_name] = qltracer.ScalarType(fq_name) elif isinstance(decl_ast, qlast.CreateLink): ctx.objects[fq_name] = qltracer.Link(fq_name, source=None, target=None) elif isinstance(decl_ast, qlast.CreateProperty): ctx.objects[fq_name] = qltracer.Property(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) elif isinstance(decl_ast, qlast.CreateGlobal): ctx.objects[fq_name] = qltracer.Global(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) # Before sorting normalize all ordering, to make sure that errors # are consistent. for ddlentry in ddlgraph.values(): ddlentry.deps = OrderedSet(sorted(ddlentry.deps)) ddlentry.weak_deps = OrderedSet(sorted(ddlentry.weak_deps)) try: ordered = topological.sort(ddlgraph, allow_unresolved=False) except topological.CycleError as e: assert isinstance(e.item, s_name.QualName) node = tracectx.ddlgraph[e.item].item item_vn = get_verbosename_from_fqname(e.item, tracectx) if e.path is not None and len(e.path): # Recursion involving more than one schema object. rec_vn = get_verbosename_from_fqname(e.path[-1], tracectx) msg = (f'definition dependency cycle between {rec_vn} ' f'and {item_vn}') else: # A single schema object with a recursive definition. msg = f'{item_vn} is defined recursively' raise errors.InvalidDefinitionError(msg, context=node.context) from e return tuple(mods) + tuple(ordered)
def _populate_concrete_constraint_attrs( self, schema: s_schema.Schema, context: sd.CommandContext, subject_obj: Optional[so.Object], *, name: sn.QualName, subjectexpr: Optional[s_expr.Expression] = None, subjectexpr_inherited: bool = False, sourcectx: Optional[c_parsing.ParserContext] = None, args: Any = None, **kwargs: Any ) -> None: from edb.ir import ast as ir_ast from edb.ir import utils as ir_utils from . import pointers as s_pointers from . import links as s_links from . import scalars as s_scalars bases = self.get_resolved_attribute_value( 'bases', schema=schema, context=context, ) if not bases: bases = self.scls.get_bases(schema) constr_base = bases.objects(schema)[0] # If we have a concrete base, then we should inherit all of # these attrs through the normal inherit_fields() mechanisms, # and populating them ourselves will just mess up # inherited_fields. if not constr_base.generic(schema): return orig_subjectexpr = subjectexpr orig_subject = subject_obj base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( f'subjectexpr is already defined for {name}' ) if (isinstance(subject_obj, s_scalars.ScalarType) and constr_base.get_is_aggregate(schema)): raise errors.InvalidConstraintDefinitionError( f'{constr_base.get_verbosename(schema)} may not ' f'be used on scalar types' ) if subjectexpr is not None: subject_ql = subjectexpr.qlast subject = subject_ql else: subject = subject_obj expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name}') # Re-parse instead of using expr.qlast, because we mutate # the AST below. expr_ql = qlparser.parse(expr.text) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) inherited = dict() if orig_subjectexpr is not None: attrs['subjectexpr'] = orig_subjectexpr inherited['subjectexpr'] = subjectexpr_inherited else: base_subjectexpr = constr_base.get_subjectexpr(schema) if base_subjectexpr is not None: attrs['subjectexpr'] = base_subjectexpr inherited['subjectexpr'] = True errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) inherited['errmessage'] = True attrs['errmessage'] = errmessage if subject is not orig_subject: # subject has been redefined assert isinstance(subject, qlast.Base) qlutils.inline_anchors( expr_ql, anchors={qlast.Subject().name: subject}) subject = orig_subject if args: args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.Subject()]), ] args_ql.extend(arg.qlast for arg in args) args_map = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) qlutils.inline_parameters(expr_ql, args_map) attrs['args'] = args assert subject is not None final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, {}), schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, path_prefix_anchor=qlast.Subject().name, apply_query_rewrites=not context.stdmode, ), ) bool_t = schema.get('std::bool', type=s_scalars.ScalarType) assert isinstance(final_expr.irast, ir_ast.Statement) expr_type = final_expr.irast.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_verbosename(schema)}', context=sourcectx ) if subjectexpr is not None: assert isinstance(subject_obj, (s_types.Type, s_pointers.Pointer)) singletons = frozenset({subject_obj}) final_subjectexpr = s_expr.Expression.compiled( subjectexpr, schema=schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, path_prefix_anchor=qlast.Subject().name, singletons=singletons, apply_query_rewrites=not context.stdmode, ), ) assert isinstance(final_subjectexpr.irast, ir_ast.Statement) refs = ir_utils.get_longest_paths(final_expr.irast) has_multi = False for ref in refs: while ref.rptr: rptr = ref.rptr if rptr.dir_cardinality.is_multi(): has_multi = True # We don't need to look further than the subject, # which is always valid. (And which is a singleton # in a constraint expression if it is itself a # singleton, regardless of other parts of the path.) if ( isinstance(rptr.ptrref, ir_ast.PointerRef) and rptr.ptrref.id == subject_obj.id ): break if (not isinstance(rptr.ptrref, ir_ast.TupleIndirectionPointerRef) and rptr.ptrref.source_ptr is None and rptr.source.rptr is not None): if isinstance(subject, s_links.Link): raise errors.InvalidConstraintDefinitionError( "link constraints may not access " "the link target", context=sourcectx ) else: raise errors.InvalidConstraintDefinitionError( "constraints cannot contain paths with more " "than one hop", context=sourcectx ) ref = rptr.source if has_multi and len(refs) > 1: raise errors.InvalidConstraintDefinitionError( "cannot reference multiple links or properties in a " "constraint where at least one link or property is MULTI", context=sourcectx ) if has_multi and ir_utils.contains_set_of_op( final_subjectexpr.irast): raise errors.InvalidConstraintDefinitionError( "cannot use aggregate functions or operators " "in a non-aggregating constraint", context=sourcectx ) attrs['finalexpr'] = final_expr attrs['params'] = constr_base.get_params(schema) inherited['params'] = True attrs['abstract'] = False for k, v in attrs.items(): self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
def type_to_typeref( schema: s_schema.Schema, t: s_types.Type, *, cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None, typename: Optional[s_name.Name] = None, include_descendants: 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*. _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 if cache is not None and typename is None: cached_result = cache.get((t.id, include_descendants)) 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 is not t: material_typeref = type_to_typeref( schema, material_type, include_descendants=include_descendants, 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 tname = t.get_name(schema) if typename is not None: name = typename else: name = tname module = schema.get_global(s_mod.Module, tname.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 descendants: Optional[FrozenSet[irast.TypeRef]] if material_typeref is None and include_descendants: descendants = frozenset( type_to_typeref( schema, child, cache=cache, ) for child in t.children(schema) if not child.get_is_derived(schema) ) else: descendants = None result = irast.TypeRef( id=t.id, module_id=module.id, name_hint=name, material_type=material_typeref, base_type=base_typeref, descendants=descendants, 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_types.Tuple) and t.is_named(schema): schema, material_type = t.material_type(schema) if material_type is not 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 is not 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[t.id, include_descendants] = result return result
def ptrref_from_ptrcls( *, schema: s_schema.Schema, ptrcls: s_pointers.PointerLike, direction: s_pointers.PointerDirection = ( s_pointers.PointerDirection.Outbound), cache: Optional[Dict[PtrRefCacheKey, irast.BasePointerRef]] = None, typeref_cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None, include_descendants: bool = False, ) -> irast.BasePointerRef: """Return an IR pointer descriptor for a given schema pointer. An IR PointerRef is an object that fully describes a schema pointer for the purposes of query compilation. Args: schema: A schema instance, in which the type *t* is defined. ptrcls: A :class:`schema.pointers.Pointer` instance for which to return the PointerRef. direction: The direction of the pointer in the path expression. Returns: An instance of a subclass of :class:`ir.ast.BasePointerRef` corresponding to the given schema pointer. """ if cache is not None: cached = cache.get((ptrcls, direction, include_descendants)) if cached is not None: return cached kwargs: Dict[str, Any] = {} ircls: Type[irast.BasePointerRef] source_ref: Optional[irast.TypeRef] target_ref: Optional[irast.TypeRef] out_source: Optional[irast.TypeRef] if isinstance(ptrcls, irast.TupleIndirectionLink): ircls = irast.TupleIndirectionPointerRef elif isinstance(ptrcls, irast.TypeIntersectionLink): ircls = irast.TypeIntersectionPointerRef kwargs['optional'] = ptrcls.is_optional() kwargs['is_empty'] = ptrcls.is_empty() kwargs['is_subtype'] = ptrcls.is_subtype() kwargs['rptr_specialization'] = ptrcls.get_rptr_specialization() elif isinstance(ptrcls, s_pointers.Pointer): ircls = irast.PointerRef kwargs['id'] = ptrcls.id name = ptrcls.get_name(schema) kwargs['module_id'] = schema.get_global( s_mod.Module, name.module).id else: raise AssertionError(f'unexpected pointer class: {ptrcls}') target = ptrcls.get_far_endpoint(schema, direction) if target is not None and not isinstance(target, irast.TypeRef): assert isinstance(target, s_types.Type) target_ref = type_to_typeref(schema, target, cache=typeref_cache) else: target_ref = target source = ptrcls.get_near_endpoint(schema, direction) source_ptr: Optional[irast.BasePointerRef] if (isinstance(ptrcls, s_props.Property) and isinstance(source, s_links.Link)): source_ptr = ptrref_from_ptrcls( ptrcls=source, direction=direction, schema=schema, cache=cache, typeref_cache=typeref_cache, ) source_ref = None else: if source is not None and not isinstance(source, irast.TypeRef): assert isinstance(source, s_types.Type) source_ref = type_to_typeref(schema, source, cache=typeref_cache) else: source_ref = source source_ptr = None if direction is s_pointers.PointerDirection.Inbound: out_source = target_ref out_target = source_ref else: out_source = source_ref out_target = target_ref out_cardinality, dir_cardinality = cardinality_from_ptrcls( schema, ptrcls, direction=direction) material_ptrcls = ptrcls.material_type(schema) material_ptr: Optional[irast.BasePointerRef] if material_ptrcls is not None and material_ptrcls is not ptrcls: material_ptr = ptrref_from_ptrcls( ptrcls=material_ptrcls, direction=direction, schema=schema, cache=cache, typeref_cache=typeref_cache, include_descendants=include_descendants, ) else: material_ptr = None union_components: Set[irast.BasePointerRef] = set() union_of = ptrcls.get_union_of(schema) union_is_concrete = False if union_of: union_ptrs = set() for component in union_of.objects(schema): assert isinstance(component, s_pointers.Pointer) material_comp = component.material_type(schema) union_ptrs.add(material_comp) non_overlapping, union_is_concrete = s_utils.get_non_overlapping_union( schema, union_ptrs, ) union_components = { ptrref_from_ptrcls( ptrcls=p, direction=direction, schema=schema, cache=cache, typeref_cache=typeref_cache, ) for p in non_overlapping } std_parent_name = None for ancestor in ptrcls.get_ancestors(schema).objects(schema): ancestor_name = ancestor.get_name(schema) if ancestor_name.module == 'std' and ancestor.generic(schema): std_parent_name = ancestor_name break is_derived = ptrcls.get_is_derived(schema) base_ptr: Optional[irast.BasePointerRef] if is_derived: base_ptrcls = ptrcls.get_bases(schema).first(schema) top_ptr_name = type(base_ptrcls).get_default_base_name() if base_ptrcls.get_name(schema) != top_ptr_name: base_ptr = ptrref_from_ptrcls( ptrcls=base_ptrcls, direction=direction, schema=schema, cache=cache, typeref_cache=typeref_cache, ) else: base_ptr = None else: base_ptr = None if ( material_ptr is None and include_descendants and isinstance(ptrcls, s_pointers.Pointer) ): descendants = frozenset( ptrref_from_ptrcls( ptrcls=child, direction=direction, schema=schema, cache=cache, typeref_cache=typeref_cache, ) for child in ptrcls.children(schema) if not child.get_is_derived(schema) ) else: descendants = frozenset() kwargs.update(dict( out_source=out_source, out_target=out_target, name=ptrcls.get_name(schema), shortname=ptrcls.get_shortname(schema), path_id_name=ptrcls.get_path_id_name(schema), std_parent_name=std_parent_name, direction=direction, source_ptr=source_ptr, base_ptr=base_ptr, material_ptr=material_ptr, descendants=descendants, is_derived=ptrcls.get_is_derived(schema), is_computable=ptrcls.get_computable(schema), union_components=union_components, union_is_concrete=union_is_concrete, has_properties=ptrcls.has_user_defined_properties(schema), dir_cardinality=dir_cardinality, out_cardinality=out_cardinality, )) ptrref = ircls(**kwargs) if cache is not None: cache[ptrcls, direction, include_descendants] = ptrref return ptrref
def get_concrete_constraint_attrs( cls, schema: s_schema.Schema, subject: Optional[so.Object], *, name: str, subjectexpr: Optional[s_expr.Expression] = None, sourcectx: Optional[c_parsing.ParserContext] = None, args: Any = None, modaliases: Optional[Mapping[Optional[str], str]] = None, **kwargs: Any ) -> Tuple[Any, Dict[str, Any], Dict[str, bool]]: # constr_base, attrs, inherited from edb.edgeql import parser as qlparser from edb.edgeql import utils as qlutils from edb.ir import ast as ir_ast constr_base: Constraint = schema.get(name, module_aliases=modaliases) module_aliases: Mapping[Optional[str], str] = {} orig_subjectexpr = subjectexpr orig_subject = subject base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr') if subjectexpr is None: subjectexpr = base_subjectexpr elif (base_subjectexpr is not None and subjectexpr.text != base_subjectexpr.text): raise errors.InvalidConstraintDefinitionError( 'subjectexpr is already defined for ' + f'{str(name)!r}') if subjectexpr is not None: subject_ql = subjectexpr.qlast if subject_ql is None: subject_ql = qlparser.parse(subjectexpr.text, module_aliases) subject = subject_ql expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr') if not expr: raise errors.InvalidConstraintDefinitionError( f'missing constraint expression in {name!r}') expr_ql = qlparser.parse(expr.text, module_aliases) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) inherited = dict() if orig_subjectexpr is not None: attrs['subjectexpr'] = orig_subjectexpr else: base_subjectexpr = constr_base.get_subjectexpr(schema) if base_subjectexpr is not None: attrs['subjectexpr'] = base_subjectexpr inherited['subjectexpr'] = True errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) inherited['errmessage'] = True attrs['errmessage'] = errmessage if subject is not orig_subject: # subject has been redefined assert isinstance(subject, qlast.Base) qlutils.inline_anchors( expr_ql, anchors={qlast.Subject().name: subject}) subject = orig_subject if args: args_map = None args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.Subject()]), ] args_ql.extend( qlparser.parse(arg.text, module_aliases) for arg in args ) args_map = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema) qlutils.inline_parameters(expr_ql, args_map) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None assert subject is not None final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, module_aliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=module_aliases, anchors={qlast.Subject().name: subject}, ), ) bool_t: s_scalars.ScalarType = schema.get('std::bool') assert isinstance(final_expr.irast, ir_ast.Statement) expr_type = final_expr.irast.stype if not expr_type.issubclass(schema, bool_t): raise errors.InvalidConstraintDefinitionError( f'{name} constraint expression expected ' f'to return a bool value, got ' f'{expr_type.get_verbosename(schema)}', context=expr_context ) attrs['return_type'] = constr_base.get_return_type(schema) attrs['return_typemod'] = constr_base.get_return_typemod(schema) attrs['finalexpr'] = final_expr attrs['params'] = constr_base.get_params(schema) attrs['is_abstract'] = False return constr_base, attrs, inherited
def object_type_to_python_type( objtype: s_objtypes.ObjectType, schema: s_schema.Schema, *, base_class: Optional[type] = None, _memo: Optional[Dict[s_types.Type, type]] = None, ) -> type: if _memo is None: _memo = {} default: Any fields = [] subclasses = [] for pn, p in objtype.get_pointers(schema).items(schema): str_pn = str(pn) if str_pn in ('id', '__type__'): continue ptype = p.get_target(schema) assert ptype is not None if isinstance(ptype, s_objtypes.ObjectType): 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) ptr_card = p.get_cardinality(schema) is_multi = ptr_card.is_multi() if is_multi: pytype = FrozenSet[pytype] # type: ignore default = p.get_default(schema) if default is None: if p.get_required(schema): default = dataclasses.MISSING else: default = qlcompiler.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', type=s_constr.Constraint) 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((str_pn, pytype, field)) bases: Tuple[type, ...] if base_class is not None: bases = (base_class,) else: bases = () ptype_dataclass = dataclasses.make_dataclass( objtype.get_name(schema).name, fields=fields, bases=bases, frozen=True, namespace={'_subclasses': subclasses}, ) assert isinstance(ptype_dataclass, type) return ptype_dataclass
def _create_begin( self, schema: s_schema.Schema, context: sd.CommandContext, ) -> s_schema.Schema: fullname = self.classname shortname = sn.shortname_from_fullname(fullname) schema, cp = self._get_param_desc_from_delta(schema, context, self) signature = f'{shortname}({", ".join(p.as_str(schema) for p in cp)})' func = schema.get(fullname, None) if func: raise errors.InvalidOperatorDefinitionError( f'cannot create the `{signature}` operator: ' f'an operator with the same signature ' f'is already defined', context=self.source_context) schema = super()._create_begin(schema, context) params: s_func.FuncParameterList = self.scls.get_params(schema) fullname = self.scls.get_name(schema) shortname = sn.shortname_from_fullname(fullname) return_typemod = self.scls.get_return_typemod(schema) assert isinstance(self.scls, Operator) recursive = self.scls.get_recursive(schema) derivative_of = self.scls.get_derivative_of(schema) # an operator must have operands if len(params) == 0: raise errors.InvalidOperatorDefinitionError( f'cannot create the `{signature}` operator: ' f'an operator must have operands', context=self.source_context) # We'll need to make sure that there's no mix of recursive and # non-recursive operators being overloaded. all_arrays = all_tuples = True for param in params.objects(schema): ptype = param.get_type(schema) all_arrays = all_arrays and ptype.is_array() all_tuples = all_tuples and ptype.is_tuple(schema) # It's illegal to declare an operator as recursive unless all # of its operands are the same basic type of collection. if recursive and not (all_arrays or all_tuples): raise errors.InvalidOperatorDefinitionError( f'cannot create the `{signature}` operator: ' f'operands of a recursive operator must either be ' f'all arrays or all tuples', context=self.source_context) for oper in schema.get_operators(shortname, ()): if oper == self.scls: continue oper_return_typemod = oper.get_return_typemod(schema) if oper_return_typemod != return_typemod: raise errors.DuplicateOperatorDefinitionError( f'cannot create the `{signature}` ' f'operator: overloading another operator with different ' f'return type {oper_return_typemod.to_edgeql()} ' f'{oper.get_return_type(schema).name}', context=self.source_context) oper_derivative_of = oper.get_derivative_of(schema) if oper_derivative_of: raise errors.DuplicateOperatorDefinitionError( f'cannot create the `{signature}` ' f'operator: there exists a derivative operator of the ' f'same name', context=self.source_context) elif derivative_of: raise errors.DuplicateOperatorDefinitionError( f'cannot create `{signature}` ' f'as a derivative operator: there already exists an ' f'operator of the same name', context=self.source_context) # Check if there is a recursive/non-recursive operator # overloading. oper_recursive = oper.get_recursive(schema) if recursive != oper_recursive: oper_signature = oper.get_display_signature(schema) oper_all_arrays = oper_all_tuples = True for param in oper.get_params(schema).objects(schema): ptype = param.get_type(schema) oper_all_arrays = oper_all_arrays and ptype.is_array() oper_all_tuples = (oper_all_tuples and ptype.is_tuple(schema)) if (all_arrays == oper_all_arrays and all_tuples == oper_all_tuples): new_rec = 'recursive' if recursive else 'non-recursive' oper_rec = \ 'recursive' if oper_recursive else 'non-recursive' raise errors.InvalidOperatorDefinitionError( f'cannot create the {new_rec} `{signature}` operator: ' f'overloading a {oper_rec} operator ' f'`{oper_signature}` with a {new_rec} one ' f'is not allowed', context=self.source_context) return schema
def generate_structure(schema: s_schema.Schema) -> SchemaReflectionParts: """Generate schema reflection structure from Python schema classes. Returns: A quadruple (as a SchemaReflectionParts instance) containing: - Delta, which, when applied to stdlib, yields an enhanced version of the `schema` module that contains all types and properties, not just those that are publicly exposed for introspection. - A mapping, containing type layout description for all schema classes. - A sequence of EdgeQL queries necessary to introspect a database schema. - A sequence of EdgeQL queries necessary to introspect global objects, such as roles and databases. """ delta = sd.DeltaRoot() classlayout: Dict[Type[s_obj.Object], SchemaTypeLayout, ] = {} ordered_link = schema.get('schema::ordered', type=s_links.Link) py_classes = [] schema = _run_ddl( ''' CREATE FUNCTION sys::_get_pg_type_for_scalar_type( typeid: std::uuid ) -> std::int64 { USING SQL $$ SELECT coalesce( ( SELECT tn::regtype::oid FROM edgedb._get_base_scalar_type_map() AS m(tid uuid, tn text) WHERE m.tid = "typeid" ), ( SELECT typ.oid FROM pg_catalog.pg_type typ WHERE typ.typname = "typeid"::text || '_domain' ), edgedb.raise( NULL::bigint, 'invalid_parameter_value', msg => ( 'cannot determine OID of ' || typeid::text ) ) )::bigint $$; SET volatility := 'STABLE'; }; CREATE FUNCTION sys::_expr_from_json( data: json ) -> OPTIONAL tuple<text: str, refs: array<uuid>> { USING SQL $$ SELECT "data"->>'text' AS text, coalesce(r.refs, ARRAY[]::uuid[]) AS refs FROM (SELECT array_agg(v::uuid) AS refs FROM jsonb_array_elements_text("data"->'refs') AS v ) AS r WHERE jsonb_typeof("data") != 'null' $$; SET volatility := 'IMMUTABLE'; }; ''', schema=schema, delta=delta, ) for py_cls in s_obj.ObjectMeta.get_schema_metaclasses(): if isinstance(py_cls, adapter.Adapter): continue if py_cls is s_obj.GlobalObject: continue py_classes.append(py_cls) read_sets: Dict[Type[s_obj.Object], List[str]] = {} for py_cls in py_classes: rschema_name = get_schema_name_for_pycls(py_cls) schema_objtype = schema.get( rschema_name, type=s_objtypes.ObjectType, default=None, ) bases = [] for base in py_cls.__bases__: if base in py_classes: bases.append(get_schema_name_for_pycls(base)) default_base = get_default_base_for_pycls(py_cls) if not bases and rschema_name != default_base: bases.append(default_base) reflection = py_cls.get_reflection_method() is_simple_wrapper = issubclass(py_cls, s_types.CollectionExprAlias) if schema_objtype is None: as_abstract = (reflection is s_obj.ReflectionMethod.REGULAR and not is_simple_wrapper) schema = _run_ddl( f''' CREATE {'ABSTRACT' if as_abstract else ''} TYPE {rschema_name} EXTENDING {', '.join(str(b) for b in bases)}; ''', schema=schema, delta=delta, ) schema_objtype = schema.get(rschema_name, type=s_objtypes.ObjectType) else: ex_bases = schema_objtype.get_bases(schema).names(schema) _, added_bases = s_inh.delta_bases(ex_bases, bases) if added_bases: for subset, position in added_bases: if isinstance(position, tuple): position_clause = (f'{position[0]} {position[1].name}') else: position_clause = position bases_expr = ', '.join(str(t.name) for t in subset) stmt = f''' ALTER TYPE {rschema_name} {{ EXTENDING {bases_expr} {position_clause} }} ''' schema = _run_ddl( stmt, schema=schema, delta=delta, ) if reflection is s_obj.ReflectionMethod.NONE: continue referrers = py_cls.get_referring_classes() if reflection is s_obj.ReflectionMethod.AS_LINK: if not referrers: raise RuntimeError( f'schema class {py_cls.__name__} is declared with AS_LINK ' f'reflection method but is not referenced in any RefDict') is_concrete = not schema_objtype.get_abstract(schema) if (is_concrete and not is_simple_wrapper and any(not b.get_abstract(schema) for b in schema_objtype.get_ancestors(schema).objects(schema))): raise RuntimeError( f'non-abstract {schema_objtype.get_verbosename(schema)} has ' f'non-abstract ancestors') read_shape = read_sets[py_cls] = [] if is_concrete: read_shape.append( '_tname := .__type__[IS schema::ObjectType].name') classlayout[py_cls] = {} ownfields = py_cls.get_ownfields() for fn, field in py_cls.get_fields().items(): if (field.ephemeral or (field.reflection_method is not s_obj.ReflectionMethod.REGULAR)): continue storage = _classify_object_field(field) ptr = schema_objtype.maybe_get_ptr(schema, sn.UnqualName(fn)) if fn in ownfields: qual = "REQUIRED" if field.required else "OPTIONAL" if ptr is None: schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ CREATE {qual} {storage.ptrkind} {fn} -> {storage.ptrtype}; }} ''', schema=schema, delta=delta, ) ptr = schema_objtype.getptr(schema, sn.UnqualName(fn)) if storage.shadow_ptrkind is not None: pn = f'{fn}__internal' internal_ptr = schema_objtype.maybe_get_ptr( schema, sn.UnqualName(pn)) if internal_ptr is None: ptrkind = storage.shadow_ptrkind ptrtype = storage.shadow_ptrtype schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ CREATE {qual} {ptrkind} {pn} -> {ptrtype}; }} ''', schema=schema, delta=delta, ) else: assert ptr is not None if is_concrete: read_ptr = fn if field.type_is_generic_self: read_ptr = f'{read_ptr}[IS {rschema_name}]' if field.reflection_proxy: proxy_type, proxy_link = field.reflection_proxy read_ptr = ( f'{read_ptr}: {{name, value := .{proxy_link}.id}}') if ptr.issubclass(schema, ordered_link): read_ptr = f'{read_ptr} ORDER BY @index' read_shape.append(read_ptr) if storage.shadow_ptrkind is not None: read_shape.append(f'{fn}__internal') if field.reflection_proxy: proxy_type_name, proxy_link_name = field.reflection_proxy proxy_obj = schema.get(proxy_type_name, type=s_objtypes.ObjectType) proxy_link_obj = proxy_obj.getptr( schema, sn.UnqualName(proxy_link_name)) tgt = proxy_link_obj.get_target(schema) else: tgt = ptr.get_target(schema) assert tgt is not None cardinality = ptr.get_cardinality(schema) assert cardinality is not None classlayout[py_cls][fn] = SchemaFieldDesc( fieldname=fn, type=tgt, cardinality=cardinality, properties={}, storage=storage, is_ordered=ptr.issubclass(schema, ordered_link), reflection_proxy=field.reflection_proxy, ) # Second pass: deal with RefDicts, which are reflected as links. for py_cls in py_classes: rschema_name = get_schema_name_for_pycls(py_cls) schema_cls = schema.get(rschema_name, type=s_objtypes.ObjectType) for refdict in py_cls.get_own_refdicts().values(): ref_ptr = schema_cls.maybe_get_ptr(schema, sn.UnqualName(refdict.attr)) ref_cls = refdict.ref_cls assert issubclass(ref_cls, s_obj.Object) shadow_ref_ptr = None reflect_as_link = (ref_cls.get_reflection_method() is s_obj.ReflectionMethod.AS_LINK) if reflect_as_link: reflection_link = ref_cls.get_reflection_link() assert reflection_link is not None target_field = ref_cls.get_field(reflection_link) target_cls = target_field.type shadow_pn = f'{refdict.attr}__internal' schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ CREATE OPTIONAL MULTI LINK {shadow_pn} EXTENDING schema::reference -> {get_schema_name_for_pycls(ref_cls)} {{ ON TARGET DELETE ALLOW; }}; }} ''', schema=schema, delta=delta, ) shadow_ref_ptr = schema_cls.getptr(schema, sn.UnqualName(shadow_pn)) else: target_cls = ref_cls if ref_ptr is None: ptr_type = get_schema_name_for_pycls(target_cls) schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ CREATE OPTIONAL MULTI LINK {refdict.attr} EXTENDING schema::reference -> {ptr_type} {{ ON TARGET DELETE ALLOW; }}; }} ''', schema=schema, delta=delta, ) ref_ptr = schema_cls.getptr(schema, sn.UnqualName(refdict.attr)) else: schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ ALTER LINK {refdict.attr} ON TARGET DELETE ALLOW; }} ''', schema=schema, delta=delta, ) assert isinstance(ref_ptr, s_links.Link) if py_cls not in classlayout: classlayout[py_cls] = {} # First, fields declared to be reflected as link properties. props = _get_reflected_link_props( ref_ptr=ref_ptr, target_cls=ref_cls, schema=schema, ) if reflect_as_link: # Then, because it's a passthrough reflection, all scalar # fields of the proxy object. fields_as_props = [ f for f in ref_cls.get_ownfields().values() if (not f.ephemeral and ( f.reflection_method is not s_obj.ReflectionMethod. AS_LINK) and f.name != refdict.backref_attr and f.name != ref_cls.get_reflection_link()) ] extra_props = _classify_scalar_object_fields(fields_as_props) for fn, storage in {**props, **extra_props}.items(): prop_ptr = ref_ptr.maybe_get_ptr(schema, sn.UnqualName(fn)) if prop_ptr is None: pty = storage.ptrtype schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ ALTER LINK {refdict.attr} {{ CREATE OPTIONAL PROPERTY {fn} -> {pty}; }} }} ''', schema=schema, delta=delta, ) if shadow_ref_ptr is not None: assert isinstance(shadow_ref_ptr, s_links.Link) shadow_pn = shadow_ref_ptr.get_shortname(schema).name for fn, storage in props.items(): prop_ptr = shadow_ref_ptr.maybe_get_ptr( schema, sn.UnqualName(fn)) if prop_ptr is None: pty = storage.ptrtype schema = _run_ddl( f''' ALTER TYPE {rschema_name} {{ ALTER LINK {shadow_pn} {{ CREATE OPTIONAL PROPERTY {fn} -> {pty}; }} }} ''', schema=schema, delta=delta, ) for py_cls in py_classes: rschema_name = get_schema_name_for_pycls(py_cls) schema_cls = schema.get(rschema_name, type=s_objtypes.ObjectType) is_concrete = not schema_cls.get_abstract(schema) read_shape = read_sets[py_cls] for refdict in py_cls.get_refdicts(): if py_cls not in classlayout: classlayout[py_cls] = {} ref_ptr = schema_cls.getptr(schema, sn.UnqualName(refdict.attr), type=s_links.Link) tgt = ref_ptr.get_target(schema) assert tgt is not None cardinality = ref_ptr.get_cardinality(schema) assert cardinality is not None classlayout[py_cls][refdict.attr] = SchemaFieldDesc( fieldname=refdict.attr, type=tgt, cardinality=cardinality, properties={}, is_ordered=ref_ptr.issubclass(schema, ordered_link), reflection_proxy=None, is_refdict=True, ) target_cls = refdict.ref_cls props = _get_reflected_link_props( ref_ptr=ref_ptr, target_cls=target_cls, schema=schema, ) reflect_as_link = (target_cls.get_reflection_method() is s_obj.ReflectionMethod.AS_LINK) prop_layout = {} extra_prop_layout = {} for fn, storage in props.items(): prop_ptr = ref_ptr.getptr(schema, sn.UnqualName(fn)) prop_tgt = prop_ptr.get_target(schema) assert prop_tgt is not None prop_layout[fn] = (prop_tgt, storage.fieldtype) if reflect_as_link: # Then, because it's a passthrough reflection, all scalar # fields of the proxy object. fields_as_props = [ f for f in target_cls.get_ownfields().values() if (not f.ephemeral and ( f.reflection_method is not s_obj.ReflectionMethod. AS_LINK) and f.name != refdict.backref_attr and f.name != target_cls.get_reflection_link()) ] extra_props = _classify_scalar_object_fields(fields_as_props) for fn, storage in extra_props.items(): prop_ptr = ref_ptr.getptr(schema, sn.UnqualName(fn)) prop_tgt = prop_ptr.get_target(schema) assert prop_tgt is not None extra_prop_layout[fn] = (prop_tgt, storage.fieldtype) else: extra_prop_layout = {} classlayout[py_cls][refdict.attr].properties.update({ **prop_layout, **extra_prop_layout, }) if reflect_as_link: shadow_tgt = schema.get( get_schema_name_for_pycls(ref_cls), type=s_objtypes.ObjectType, ) classlayout[py_cls][f'{refdict.attr}__internal'] = ( SchemaFieldDesc( fieldname=refdict.attr, type=shadow_tgt, cardinality=qltypes.SchemaCardinality.Many, properties=prop_layout, is_refdict=True, )) if is_concrete: read_ptr = refdict.attr prop_shape_els = [] if reflect_as_link: read_ptr = f'{read_ptr}__internal' ref_ptr = schema_cls.getptr( schema, sn.UnqualName(f'{refdict.attr}__internal'), ) for fn in props: prop_shape_els.append(f'@{fn}') if prop_shape_els: prop_shape = ',\n'.join(prop_shape_els) read_ptr = f'{read_ptr}: {{id, {prop_shape}}}' if ref_ptr.issubclass(schema, ordered_link): read_ptr = f'{read_ptr} ORDER BY @index' read_shape.append(read_ptr) local_parts = [] global_parts = [] for py_cls, shape_els in read_sets.items(): if (not shape_els # The CollectionExprAlias family needs to be excluded # because TupleExprAlias and ArrayExprAlias inherit from # concrete classes and so are picked up from those. or issubclass(py_cls, s_types.CollectionExprAlias)): continue rschema_name = get_schema_name_for_pycls(py_cls) shape = ',\n'.join(shape_els) qry = f''' SELECT {rschema_name} {{ {shape} }} ''' if not issubclass(py_cls, (s_types.Collection, s_obj.GlobalObject)): qry += ' FILTER NOT .builtin' if issubclass(py_cls, s_obj.GlobalObject): global_parts.append(qry) else: local_parts.append(qry) delta.canonical = True return SchemaReflectionParts( intro_schema_delta=delta, class_layout=classlayout, local_intro_parts=local_parts, global_intro_parts=global_parts, )
def init_context( *, schema: s_schema.Schema, func_params: Optional[s_func.ParameterLikeList]=None, parent_object_type: Optional[s_obj.ObjectMeta]=None, modaliases: Optional[Mapping[Optional[str], str]]=None, anchors: Optional[ Mapping[ Union[str, qlast.SpecialAnchorT], Union[s_obj.Object, irast.Base], ], ]=None, singletons: Optional[Iterable[s_types.Type]]=None, security_context: Optional[str]=None, derived_target_module: Optional[str]=None, result_view_name: Optional[s_name.SchemaName]=None, schema_view_mode: bool=False, disable_constant_folding: bool=False, allow_generic_type_output: bool=False, implicit_limit: int=0, implicit_id_in_shapes: bool=False, implicit_tid_in_shapes: bool=False, json_parameters: bool=False, session_mode: bool=False) -> \ context.ContextLevel: if not schema.get_global(s_mod.Module, '__derived__', None): schema, _ = s_mod.Module.create_in_schema(schema, name='__derived__') env = context.Environment( schema=schema, path_scope=irast.new_scope_tree(), constant_folding=not disable_constant_folding, func_params=func_params, parent_object_type=parent_object_type, schema_view_mode=schema_view_mode, json_parameters=json_parameters, session_mode=session_mode, allow_generic_type_output=allow_generic_type_output) ctx = context.ContextLevel(None, context.ContextSwitchMode.NEW, env=env) _ = context.CompilerContext(initial=ctx) if singletons: # The caller wants us to treat these type references # as singletons for the purposes of the overall expression # cardinality inference, so we set up the scope tree in # the necessary fashion. for singleton in singletons: path_id = pathctx.get_path_id(singleton, ctx=ctx) ctx.env.path_scope.attach_path(path_id) ctx.path_scope = ctx.env.path_scope.attach_fence() if modaliases: ctx.modaliases.update(modaliases) if anchors: with ctx.newscope(fenced=True) as subctx: populate_anchors(anchors, ctx=subctx) ctx.derived_target_module = derived_target_module ctx.toplevel_result_view_name = result_view_name ctx.implicit_id_in_shapes = implicit_id_in_shapes ctx.implicit_tid_in_shapes = implicit_tid_in_shapes ctx.implicit_limit = implicit_limit return ctx