def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, track_schema_ref_exprs: bool = False, ) -> s_expr.Expression: if field.name in {'expr', 'condition'}: parent_ctx = self.get_referrer_context_or_die(context) source = parent_ctx.op.get_object(schema, context) parent_vname = source.get_verbosename(schema) pol_name = self.get_verbosename(parent=parent_vname) in_ddl_context_name = pol_name assert isinstance(source, s_types.Type) return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors={qlast.Subject().name: source}, path_prefix_anchor=qlast.Subject().name, singletons=frozenset({source}), apply_query_rewrites=not context.stdmode, track_schema_ref_exprs=track_schema_ref_exprs, in_ddl_context_name=in_ddl_context_name, ), ) else: return super().compile_expr_field(schema, context, field, value, track_schema_ref_exprs)
def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, track_schema_ref_exprs: bool = False, ) -> s_expr.Expression: from . import objtypes as s_objtypes singletons: List[s_types.Type] if field.name == 'expr': # type ignore below, for the class is used as mixin parent_ctx = context.get_ancestor( IndexSourceCommandContext, # type: ignore self) assert parent_ctx is not None assert isinstance(parent_ctx.op, sd.ObjectCommand) subject = parent_ctx.op.get_object(schema, context) if isinstance(subject, s_abc.Pointer): singletons = [] path_prefix_anchor = None else: assert isinstance(subject, s_objtypes.ObjectType) singletons = [subject] path_prefix_anchor = qlast.Subject().name expr = type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, singletons=frozenset(singletons), apply_query_rewrites=not context.stdmode, track_schema_ref_exprs=track_schema_ref_exprs, ), ) # Check that the inferred cardinality is no more than 1 from edb.ir import ast as ir_ast assert isinstance(expr.irast, ir_ast.Statement) if expr.irast.cardinality.is_multi(): raise errors.ResultCardinalityMismatchError( f'possibly more than one element returned by ' f'the index expression where only singletons ' f'are allowed') return expr else: return super().compile_expr_field(schema, context, field, value, track_schema_ref_exprs)
def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, ) -> s_expr.Expression: from . import objtypes as s_objtypes singletons: List[s_types.Type] if field.name == 'expr': # type ignore below, for the class is used as mixin parent_ctx = context.get_ancestor( IndexSourceCommandContext, # type: ignore self) assert parent_ctx is not None subject_name = parent_ctx.op.classname subject = schema.get(subject_name, default=None) if isinstance(subject, s_abc.Pointer): singletons = [] path_prefix_anchor = None else: assert isinstance(subject, s_objtypes.ObjectType) singletons = [subject] path_prefix_anchor = qlast.Subject().name return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, singletons=frozenset(singletons), ), ) else: return super().compile_expr_field(schema, context, field, value)
def compile_expr_field(self, schema, context, field, value): if field.name == 'expr': parent_ctx = context.get_ancestor(IndexSourceCommandContext, self) subject_name = parent_ctx.op.classname subject = schema.get(subject_name, default=None) if not isinstance(subject, s_abc.Pointer): singletons = [subject] path_prefix_anchor = qlast.Subject().name else: singletons = [] path_prefix_anchor = None return type(value).compiled( value, schema=schema, modaliases=context.modaliases, parent_object_type=self.get_schema_metaclass(), anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, singletons=singletons, ) else: return super().compile_expr_field(schema, context, field, value)
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 reduce_DUNDERSUBJECT(self, *kids): self.val = qlast.Path(steps=[qlast.Subject()])
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 compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, track_schema_ref_exprs: bool=False, ) -> s_expr.Expression: base: Optional[so.Object] = None if isinstance(self, AlterConstraint): base = self.scls.get_subject(schema) else: referrer_ctx = self.get_referrer_context(context) if referrer_ctx: base = referrer_ctx.op.scls if base is not None: # Concrete constraint if field.name == 'expr': # Concrete constraints cannot redefine the base check # expressions, and so the only way we should get here # is through field inheritance, so check that the # value is compiled and move on. if not value.is_compiled(): mcls = self.get_schema_metaclass() dn = mcls.get_schema_class_displayname() raise errors.InternalServerError( f'uncompiled expression in the {field.name!r} field of' f' {dn} {self.classname!r}' ) return value elif field.name in {'subjectexpr', 'finalexpr'}: return s_expr.Expression.compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Subject().name: base}, path_prefix_anchor=qlast.Subject().name, allow_generic_type_output=True, schema_object_context=self.get_schema_metaclass(), apply_query_rewrites=not context.stdmode, track_schema_ref_exprs=track_schema_ref_exprs, ), ) else: return super().compile_expr_field( schema, context, field, value) elif field.name in ('expr', 'subjectexpr'): # Abstract constraint. params = self._get_params(schema, context) param_anchors = s_func.get_params_symtable( params, schema, inlined_defaults=False, ) return s_expr.Expression.compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors=param_anchors, func_params=params, allow_generic_type_output=True, schema_object_context=self.get_schema_metaclass(), apply_query_rewrites=not context.stdmode, track_schema_ref_exprs=track_schema_ref_exprs, ), ) else: return super().compile_expr_field( schema, context, field, value, track_schema_ref_exprs)
def schema_constraint_to_backend_constraint(cls, subject, constraint, schema): assert constraint.get_subject(schema) is not None ir = qlcompiler.compile_ast_to_ir( constraint.get_finalexpr(schema).qlast, schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, ), ) terminal_refs = ir_utils.get_longest_paths(ir.expr.expr.result) ref_tables = cls._get_ref_storage_info(ir.schema, terminal_refs) if len(ref_tables) > 1: raise ValueError( 'backend: multi-table constraints are not currently supported') elif ref_tables: subject_db_name, refs = next(iter(ref_tables.items())) link_bias = refs[0][3].table_type == 'link' else: subject_db_name = common.get_backend_name(schema, subject, catenate=False) link_bias = False exclusive_expr_refs = cls._get_exclusive_refs(ir) pg_constr_data = { 'subject_db_name': subject_db_name, 'expressions': [] } constraint_origin = cls._get_constraint_origin(schema, constraint) if constraint_origin != constraint: origin_subject = constraint_origin.get_subject(schema) origin_ir = qlcompiler.compile_ast_to_ir( constraint_origin.get_finalexpr(schema).qlast, schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: origin_subject}, ), ) origin_terminal_refs = ir_utils.get_longest_paths( origin_ir.expr.expr.result) origin_ref_tables = cls._get_ref_storage_info( origin_ir.schema, origin_terminal_refs) if origin_ref_tables: origin_subject_db_name, _ = (next( iter(origin_ref_tables.items()))) else: origin_subject_db_name = common.get_backend_name( schema, origin_subject, catenate=False, ) pg_constr_data['origin_subject_db_name'] = origin_subject_db_name else: pg_constr_data['origin_subject_db_name'] = subject_db_name exprs = pg_constr_data['expressions'] if exclusive_expr_refs: for ref in exclusive_expr_refs: exprdata = cls._edgeql_ref_to_pg_constr( subject, ref, schema, link_bias) exprs.append(exprdata) pg_constr_data['scope'] = 'relation' pg_constr_data['type'] = 'unique' else: exprdata = cls._edgeql_ref_to_pg_constr(subject, ir, schema, link_bias) exprs.append(exprdata) pg_constr_data['scope'] = 'row' pg_constr_data['type'] = 'check' if isinstance(constraint.get_subject(schema), s_scalars.ScalarType): constraint = SchemaDomainConstraint(subject=subject, constraint=constraint, pg_constr_data=pg_constr_data, schema=schema) else: constraint = SchemaTableConstraint(subject=subject, constraint=constraint, pg_constr_data=pg_constr_data, schema=schema) return constraint
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 schema_constraint_to_backend_constraint(cls, subject, constraint, schema, context, source_context): assert constraint.get_subject(schema) is not None constraint_origin = cls._get_constraint_origin(schema, constraint) if constraint_origin != constraint: origin_subject = constraint_origin.get_subject(schema) else: origin_subject = subject path_prefix_anchor = (qlast.Subject().name if isinstance( subject, s_types.Type) else None) ir = qlcompiler.compile_ast_to_ir( constraint.get_finalexpr(schema).qlast, schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: subject}, path_prefix_anchor=path_prefix_anchor, apply_query_rewrites=not context.stdmode, ), ) terminal_refs = ir_utils.get_longest_paths(ir.expr.expr.result) ref_tables = get_ref_storage_info(ir.schema, terminal_refs) if len(ref_tables) > 1: raise errors.InvalidConstraintDefinitionError( f'Constraint {constraint.get_displayname(schema)} on ' f'{subject.get_displayname(schema)} is not supported ' f'because it would depend on multiple objects', context=source_context, ) elif ref_tables: subject_db_name, _ = next(iter(ref_tables.items())) else: subject_db_name = common.get_backend_name(schema, subject, catenate=False) exclusive_expr_refs = cls._get_exclusive_refs(ir) pg_constr_data = { 'subject_db_name': subject_db_name, 'expressions': [], 'origin_expressions': [], } if constraint_origin != constraint: origin_path_prefix_anchor = (qlast.Subject().name if isinstance( origin_subject, s_types.Type) else None) origin_ir = qlcompiler.compile_ast_to_ir( constraint_origin.get_finalexpr(schema).qlast, schema, options=qlcompiler.CompilerOptions( anchors={qlast.Subject().name: origin_subject}, path_prefix_anchor=origin_path_prefix_anchor, apply_query_rewrites=not context.stdmode, ), ) origin_terminal_refs = ir_utils.get_longest_paths( origin_ir.expr.expr.result) origin_ref_tables = get_ref_storage_info(origin_ir.schema, origin_terminal_refs) if origin_ref_tables: origin_subject_db_name, _ = (next( iter(origin_ref_tables.items()))) else: origin_subject_db_name = common.get_backend_name( schema, origin_subject, catenate=False, ) origin_exclusive_expr_refs = cls._get_exclusive_refs(origin_ir) pg_constr_data['origin_subject_db_name'] = origin_subject_db_name else: origin_exclusive_expr_refs = None pg_constr_data['origin_subject_db_name'] = subject_db_name if exclusive_expr_refs: for ref in exclusive_expr_refs: exprdata = cls._edgeql_ref_to_pg_constr( subject, origin_subject, ref, schema) pg_constr_data['expressions'].append(exprdata) if origin_exclusive_expr_refs: for ref in origin_exclusive_expr_refs: exprdata = cls._edgeql_ref_to_pg_constr( subject, origin_subject, ref, schema) pg_constr_data['origin_expressions'].append(exprdata) else: pg_constr_data['origin_expressions'] = ( pg_constr_data['expressions']) pg_constr_data['scope'] = 'relation' pg_constr_data['type'] = 'unique' else: exprdata = cls._edgeql_ref_to_pg_constr(subject, origin_subject, ir, schema) pg_constr_data['expressions'].append(exprdata) pg_constr_data['scope'] = 'row' pg_constr_data['type'] = 'check' if isinstance(constraint.get_subject(schema), s_scalars.ScalarType): constraint = SchemaDomainConstraint(subject=subject, constraint=constraint, pg_constr_data=pg_constr_data, schema=schema) else: constraint = SchemaTableConstraint(subject=subject, constraint=constraint, pg_constr_data=pg_constr_data, schema=schema) return constraint