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)
async def compile_graphql( self, dbver: bytes, gql: str, tokens: Optional[List[Tuple[gql_lexer.TokenKind, int, int, int, int, str]]], substitutions: Optional[Dict[str, Tuple[str, int, int]]], operation_name: str = None, variables: Optional[Mapping[str, object]] = None, ) -> CompiledOperation: db = await self._get_database(dbver) if tokens is None: ast = graphql.parse_text(gql) else: ast = graphql.parse_tokens(gql, tokens) op = graphql.translate_ast( db.gqlcore, # type: ignore[attr-defined] ast, variables=variables, substitutions=substitutions, operation_name=operation_name) ir = qlcompiler.compile_ast_to_ir( op.edgeql_ast, schema=db.schema, options=qlcompiler.CompilerOptions( json_parameters=True, allow_top_level_shape_dml=True, ), ) if ir.cardinality.is_multi(): raise errors.ResultCardinalityMismatchError( f'compiled GrqphQL query has cardinality {ir.cardinality}, ' f'expected ONE') sql_text, argmap = pg_compiler.compile_ir_to_sql( ir, pretty=bool(debug.flags.edgeql_compile), expected_cardinality_one=True, output_format=pg_compiler.OutputFormat.JSON) args: List[Optional[str]] = [None] * len(argmap) for argname, param in argmap.items(): args[param.index - 1] = argname sql_bytes = sql_text.encode() sql_hash = self._hash_sql(sql_bytes) return CompiledOperation( sql=sql_bytes, sql_hash=sql_hash, sql_args=args, # type: ignore[arg-type] # XXX: optional bug? dbver=dbver, cacheable=op.cacheable, cache_deps_vars=op.cache_deps_vars, variables=op.variables_desc, )
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema, options=compiler.CompilerOptions( apply_query_rewrites=False, modaliases={None: 'default'}, )) root = ir.scope_tree if len(root.children) != 1: self.fail( f'Scope tree root is expected to have only one child, got' f' {len(root.children)}' f' \n{root.pformat()}') scope_tree = next(iter(root.children)) path_scope = textwrap.indent(scope_tree.pformat(), ' ') expected_scope = textwrap.indent( textwrap.dedent(expected).strip(' \n'), ' ') if path_scope != expected_scope: diff = '\n'.join( difflib.context_diff(expected_scope.split('\n'), path_scope.split('\n'))) self.fail(f'Scope tree does not match the expected result.' f'\nEXPECTED:\n{expected_scope}\nACTUAL:\n{path_scope}' f'\nDIFF:\n{diff}')
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 {'default', 'expr'}: ptr_name = self.get_verbosename() in_ddl_context_name = None if field.name == 'expr': in_ddl_context_name = f'computed {ptr_name}' return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), 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_alias_expr( self, expr: qlast.Base, classname: sn.SchemaName, schema: s_schema.Schema, context: sd.CommandContext, ) -> irast.Statement: cached: Optional[irast.Statement] = (context.get_cached( (expr, classname))) if cached is not None: return cached if not isinstance(expr, qlast.Statement): expr = qlast.SelectQuery(result=expr) existing = schema.get(classname, type=s_types.Type, default=None) if existing is not None: drop_cmd = existing.init_delta_command(schema, sd.DeleteObject) with context.suspend_dep_verification(): schema = drop_cmd.apply(schema, context) ir = qlcompiler.compile_ast_to_ir( expr, schema, options=qlcompiler.CompilerOptions( derived_target_module=classname.module, result_view_name=classname, modaliases=context.modaliases, schema_view_mode=True, ), ) context.cache_value((expr, classname), ir) return ir # type: ignore
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_to_tree(self, source): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir( qltree, self.schema, options=compiler.CompilerOptions(modaliases={None: 'default'}, ), ) return pg_compiler.compile_ir_to_sql_tree( ir, output_format=pg_compiler.OutputFormat.NATIVE, )
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir( qltree, self.schema, options=compiler.CompilerOptions(validate_multiplicity=True)) # The expected multiplicity is given for the whole query. exp = textwrap.dedent(expected).strip(' \n') expected_multiplicity = qltypes.Multiplicity(exp) self.assertEqual(ir.multiplicity, expected_multiplicity, 'unexpected multiplicity:\n' + source)
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir( qltree, self.schema, options=compiler.CompilerOptions( modaliases={None: 'default'}, ), ) expected_volatility = qltypes.Volatility( textwrap.dedent(expected).strip(' \n')) self.assertEqual(ir.volatility, expected_volatility, 'unexpected volatility:\n' + source)
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema, options=compiler.CompilerOptions( apply_query_rewrites=False, modaliases={None: 'default'}, )) root = ir.scope_tree if len(root.children) != 1: self.fail( f'Scope tree root is expected to have only one child, got' f' {len(root.children)}' f' \n{root.pformat()}')
def from_ast( cls, schema: s_schema.Schema, modaliases: Mapping[Optional[str], str], num: int, astnode: qlast.FuncParam, ) -> ParameterDesc: paramd = None if astnode.default is not None: defexpr = expr.Expression.from_ast( astnode.default, schema, modaliases, as_fragment=True) paramd = expr.Expression.compiled( defexpr, schema, as_fragment=True, options=qlcompiler.CompilerOptions( modaliases=modaliases, ) ) paramt_ast = astnode.type if astnode.kind is ft.ParameterKind.VARIADIC: paramt_ast = qlast.TypeName( maintype=qlast.ObjectRef( name='array', ), subtypes=[paramt_ast], ) assert isinstance(paramt_ast, qlast.TypeName) paramt = utils.ast_to_type_shell( paramt_ast, modaliases=modaliases, schema=schema, ) return cls( num=num, name=astnode.name, type=paramt, typemod=astnode.typemod, kind=astnode.kind, default=paramd )
def _compile_alias_expr( self, expr: qlast.Base, classname: sn.QualName, schema: s_schema.Schema, context: sd.CommandContext, ) -> irast.Statement: cached: Optional[irast.Statement] = ( context.get_cached((expr, classname))) if cached is not None: return cached if not isinstance(expr, qlast.Statement): expr = qlast.SelectQuery(result=expr) existing = schema.get(classname, type=s_types.Type, default=None) if existing is not None: drop_cmd = existing.init_delta_command(schema, sd.DeleteObject) with context.suspend_dep_verification(): schema = drop_cmd.apply(schema, context) ir = qlcompiler.compile_ast_to_ir( expr, schema, options=qlcompiler.CompilerOptions( derived_target_module=classname.module, result_view_name=classname, modaliases=context.modaliases, schema_view_mode=True, in_ddl_context_name='alias definition', ), ) if ir.volatility == qltypes.Volatility.Volatile: srcctx = self.get_attribute_source_context('expr') raise errors.SchemaDefinitionError( f'volatile functions are not permitted in schema-defined ' f'computables', context=srcctx ) context.cache_value((expr, classname), ir) return ir # type: ignore
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir( qltree, self.schema, options=compiler.CompilerOptions( modaliases={None: 'default'}, ), ) if not expected: return # The expected cardinality is either given for the whole query # (by default) or for a specific element of the top-level # shape. In case of the specific element the name of the shape # element must be given followed by ":" and then the # cardinality. exp = textwrap.dedent(expected).strip(' \n').split(':') if len(exp) == 1: field = None expected_cardinality = qltypes.Cardinality(exp[0]) elif len(exp) == 2: field = exp[0].strip() expected_cardinality = qltypes.Cardinality(exp[1].strip()) else: raise ValueError( f'unrecognized expected specification: {expected!r}') if field is not None: shape = ir.expr.expr.result.shape for el, _ in shape: if str(el.path_id.rptr_name()).endswith(field): card = el.rptr.ptrref.out_cardinality self.assertEqual(card, expected_cardinality, 'unexpected cardinality:\n' + source) break else: raise AssertionError(f'shape field not found: {field!r}') else: self.assertEqual(ir.cardinality, expected_cardinality, 'unexpected cardinality:\n' + source)
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 sources as s_sources if field.name in {'default', 'expr'}: singletons: List[s_types.Type] = [] path_prefix_anchor = None anchors: Dict[str, Any] = {} if field.name == 'expr': # type ignore below, because the class is used as mixin parent_ctx = context.get_ancestor( s_sources.SourceCommandContext, # type: ignore self ) assert parent_ctx is not None assert isinstance(parent_ctx.op, sd.ObjectCommand) source_name = parent_ctx.op.classname source = schema.get(source_name, default=None) anchors[qlast.Source().name] = source if not isinstance(source, Pointer): assert source is not None singletons = [source] path_prefix_anchor = qlast.Source().name return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, schema_object_context=self.get_schema_metaclass(), anchors=anchors, 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: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: expr.Expression, ) -> expr.Expression: if field.name == 'initial_value': return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( allow_generic_type_output=True, schema_object_context=self.get_schema_metaclass(), ), ) elif field.name == 'nativecode': return self.compile_function(schema, context, value) else: return super().compile_expr_field(schema, context, field, value)
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: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, ) -> s_expr.Expression: if field.name in ('expr', 'subjectexpr'): if not isinstance(self, CreateConstraint): raise TypeError("ALTER-ing constraint expressions " "is not supported") params = self._get_params(schema, context) anchors: Dict[str, Any] = {} param_anchors = s_func.get_params_symtable( params, schema, inlined_defaults=False, ) anchors.update(param_anchors) referrer_ctx = self.get_referrer_context(context) if referrer_ctx is not None: assert isinstance(referrer_ctx.op, sd.ObjectCommand) anchors['__subject__'] = referrer_ctx.op.scls return s_expr.Expression.compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors=anchors, func_params=params, allow_generic_type_output=True, schema_object_context=self.get_schema_metaclass(), ), ) else: return super().compile_expr_field(schema, context, field, value)
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: assert field.name == 'expr' classname = sn.shortname_from_fullname(self.classname) assert isinstance(classname, sn.QualName), \ "expected qualified name" return type(value).compiled( value, schema=schema, options=qlcompiler.CompilerOptions( derived_target_module=classname.module, modaliases=context.modaliases, in_ddl_context_name='alias definition', track_schema_ref_exprs=track_schema_ref_exprs, ), )
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils from edb.schema import objtypes as s_objtypes # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) assert parent_ctx is not None source_name = parent_ctx.op.classname source = schema.get(source_name, type=s_objtypes.ObjectType) expression = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=frozenset([source]), ), ) assert isinstance(expression.irast, irast.Statement) base = None target = expression.irast.stype result_expr = expression.irast.expr.expr if (isinstance(result_expr, irast.SelectStmt) and result_expr.result.rptr is not None): expr_rptr = result_expr.result.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound) if is_ptr_alias: new_schema, base = irtyputils.ptrcls_from_ptrref( expr_rptr.ptrref, schema=schema) # Only pointers coming from the same source as the # alias should be "inherited" (in order to preserve # link props). Random paths coming from other sources # get treated same as any other arbitrary expression # in a computable. if base.get_source(new_schema) != source: base = None else: schema = new_schema self.set_attribute_value('expr', expression) required, card = expression.irast.cardinality.to_schema_value() spec_required = self.get_attribute_value('required') spec_card = self.get_attribute_value('cardinality') if spec_required and not required: ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly an empty set returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx) if (spec_card in {None, qltypes.SchemaCardinality.ONE} and card is not qltypes.SchemaCardinality.ONE): ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.SchemaDefinitionError( f'possibly more than one element returned by an ' f'expression for the computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx) if spec_card is None: self.set_attribute_value('cardinality', card) if not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('computable', True) return schema, target, base
def compile_graphql( std_schema: s_schema.FlatSchema, user_schema: s_schema.FlatSchema, global_schema: s_schema.FlatSchema, database_config: Mapping[str, Any], system_config: Mapping[str, Any], gql: str, tokens: Optional[List[Tuple[gql_lexer.TokenKind, int, int, int, int, str]]], substitutions: Optional[Dict[str, Tuple[str, int, int]]], operation_name: str = None, variables: Optional[Mapping[str, object]] = None, ) -> CompiledOperation: if tokens is None: ast = graphql.parse_text(gql) else: ast = graphql.parse_tokens(gql, tokens) gqlcore = _get_gqlcore(std_schema, user_schema, global_schema) op = graphql.translate_ast(gqlcore, ast, variables=variables, substitutions=substitutions, operation_name=operation_name) ir = qlcompiler.compile_ast_to_ir( op.edgeql_ast, schema=s_schema.ChainedSchema( std_schema, user_schema, global_schema, ), options=qlcompiler.CompilerOptions( json_parameters=True, allow_top_level_shape_dml=True, ), ) if ir.cardinality.is_multi(): raise errors.ResultCardinalityMismatchError( f'compiled GrqphQL query has cardinality {ir.cardinality}, ' f'expected ONE') sql_text, argmap = pg_compiler.compile_ir_to_sql( ir, pretty=bool(debug.flags.edgeql_compile), expected_cardinality_one=True, output_format=pg_compiler.OutputFormat.JSON) args: List[Optional[str]] = [None] * len(argmap) for argname, param in argmap.items(): args[param.index - 1] = argname sql_bytes = sql_text.encode() sql_hash = hashlib.sha1(sql_bytes).hexdigest().encode('latin1') return CompiledOperation( sql=sql_bytes, sql_hash=sql_hash, sql_args=args, # type: ignore[arg-type] # XXX: optional bug? cacheable=op.cacheable, cache_deps_vars=op.cache_deps_vars, variables=op.variables_desc, )
def compile_expr_field( self, schema: s_schema.Schema, context: sd.CommandContext, field: so.Field[Any], value: s_expr.Expression, ) -> s_expr.Expression: referrer_ctx = self.get_referrer_context(context) if referrer_ctx 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'}: anchors = {'__subject__': referrer_ctx.op.scls} return s_expr.Expression.compiled( value, schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors=anchors, allow_generic_type_output=True, schema_object_context=self.get_schema_metaclass(), ), ) 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(), ), ) else: return super().compile_expr_field(schema, context, field, value)
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 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
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 compile_function( self, schema: s_schema.Schema, context: sd.CommandContext, body: expr.Expression, ) -> expr.Expression: from edb.ir import ast as irast params = self._get_params(schema, context) session_only = self._get_attribute_value( schema, context, 'session_only') language = self._get_attribute_value(schema, context, 'language') assert language is qlast.Language.EdgeQL has_inlined_defaults = bool(params.find_named_only(schema)) param_anchors = get_params_symtable( params, schema, inlined_defaults=has_inlined_defaults, ) compiled = type(body).compiled( body, schema, options=qlcompiler.CompilerOptions( anchors=param_anchors, func_params=params, # the body of a session_only function can contain calls to # other session_only functions session_mode=session_only, ), ) ir = compiled.irast assert isinstance(ir, irast.Statement) schema = ir.schema return_type = self._get_attribute_value(schema, context, 'return_type') if (not ir.stype.issubclass(schema, return_type) and not ir.stype.implicitly_castable_to(return_type, schema)): raise errors.InvalidFunctionDefinitionError( f'return type mismatch in function declared to return ' f'{return_type.get_verbosename(schema)}', details=f'Actual return type is ' f'{ir.stype.get_verbosename(schema)}', context=body.qlast.context, ) return_typemod = self._get_attribute_value( schema, context, 'return_typemod') if (return_typemod is not ft.TypeModifier.SET_OF and ir.cardinality.is_multi()): raise errors.InvalidFunctionDefinitionError( f'return cardinality mismatch in function declared to return ' f'a singleton', details=( f'Function may return a set with more than one element.' ), context=body.qlast.context, ) return compiled
def _parse_computable( self, expr: qlast.Base, schema: s_schema.Schema, context: sd.CommandContext, ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]: from edb.ir import ast as irast from edb.ir import typeutils as irtyputils from edb.schema import objtypes as s_objtypes # "source" attribute is set automatically as a refdict back-attr parent_ctx = self.get_referrer_context(context) assert parent_ctx is not None source_name = parent_ctx.op.classname source = schema.get(source_name, type=s_objtypes.ObjectType) expression = s_expr.Expression.compiled( s_expr.Expression.from_ast(expr, schema, context.modaliases), schema=schema, options=qlcompiler.CompilerOptions( modaliases=context.modaliases, anchors={qlast.Source().name: source}, path_prefix_anchor=qlast.Source().name, singletons=frozenset([source]), ), ) assert isinstance(expression.irast, irast.Statement) base = None target = expression.irast.stype result_expr = expression.irast.expr.expr if (isinstance(result_expr, irast.SelectStmt) and result_expr.result.rptr is not None): expr_rptr = result_expr.result.rptr while isinstance(expr_rptr, irast.TypeIntersectionPointer): expr_rptr = expr_rptr.source.rptr is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound) if is_ptr_alias: schema, base = irtyputils.ptrcls_from_ptrref(expr_rptr.ptrref, schema=schema) self.set_attribute_value('expr', expression) required, card = expression.irast.cardinality.to_schema_value() spec_required = self.get_attribute_value('required') spec_card = self.get_attribute_value('cardinality') # If cardinality was unspecified and the computable is not # required, use the inferred cardinality. if spec_card is None and not spec_required: self.set_attribute_value('required', required) self.set_attribute_value('cardinality', card) else: # Otherwise honor the spec, so no cardinality change, but check # that it's valid. if spec_card is None: # A computable link is marked explicitly as # "required", so we assume that omitted cardinality is # "single". Basically, to infer the cardinality both # cardinality-related qualifiers need to be omitted. spec_card = qltypes.SchemaCardinality.ONE if spec_required and not required: ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly an empty set returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'required'", context=srcctx) if (spec_card is qltypes.SchemaCardinality.ONE and card != spec_card): ptr_name = sn.shortname_from_fullname( self.get_attribute_value('name')).name srcctx = self.get_attribute_source_context('target') raise errors.QueryError( f'possibly more than one element returned by an ' f'expression for a computable ' f'{ptr_name!r} ' f"declared as 'single'", context=srcctx) self.set_attribute_value('computable', True) return schema, target, base
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 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