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 get_concrete_constraint_attrs( cls, schema, subject, *, name, subjectexpr=None, sourcectx=None, args=[], modaliases=None, **kwargs): from edb.edgeql import parser as qlparser from edb.edgeql import utils as qlutils constr_base = schema.get(name, module_aliases=modaliases) module_aliases = {} 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 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 qlutils.inline_anchors(expr_ql, anchors={qlast.Subject: subject}) subject = orig_subject args_map = None if args: args_ql = [ 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) args_map = {name: edgeql.generate_source(val, pretty=False) for name, val in args_map.items()} args_map['__subject__'] = '{__subject__}' attrs['errmessage'] = attrs['errmessage'].format(**args_map) inherited.pop('errmessage', None) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None final_expr = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr_ql, schema, module_aliases), schema=schema, modaliases=module_aliases, anchors={qlast.Subject: subject}, ) bool_t = schema.get('std::bool') 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_name(schema).name!r}', 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) return constr_base, attrs, inherited
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 _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 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 get_concrete_constraint_attrs(cls, schema, subject, *, name, subjectexpr=None, sourcectx=None, args=[], modaliases=None, **kwargs): from edb.edgeql import utils as edgeql_utils from edb.edgeql import parser as edgeql_parser constr_base = schema.get(name, module_aliases=modaliases) module_aliases = {} 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, _ = cls._normalize_constraint_expr( schema, {}, subjectexpr.text, subject) 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 = edgeql_parser.parse(expr.text, module_aliases) if not args: args = constr_base.get_field_value(schema, 'args') attrs = dict(kwargs) args_map = None if args: args_ql = [ edgeql_parser.parse(arg.text, module_aliases) for arg in args ] args_map = edgeql_utils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema) edgeql_utils.inline_parameters(expr_ql, args_map) args_map = { name: edgeql.generate_source(val, pretty=False) for name, val in args_map.items() } errmessage = attrs.get('errmessage') if not errmessage: errmessage = constr_base.get_errmessage(schema) attrs['errmessage'] = errmessage.format( __subject__='{__subject__}', **args_map) attrs['args'] = args if expr == '__subject__': expr_context = sourcectx else: expr_context = None if subject is not orig_subject: # subject has been redefined edgeql_utils.inline_anchors(expr_ql, anchors={qlast.Subject: subject}) subject = orig_subject expr_text = cls.normalize_constraint_expr(schema, module_aliases, expr_ql, subject=subject, constraint_name=name, enforce_boolean=True, expr_context=expr_context) attrs['finalexpr'] = s_expr.Expression(text=expr_text, origtext=expr_text) return constr_base, attrs