def format_error_message( self, schema: s_schema.Schema, ) -> str: errmsg = self.get_errmessage(schema) subject = self.get_subject(schema) titleattr = subject.get_annotation(schema, 'std::title') if not titleattr: subjname = subject.get_shortname(schema) subjtitle = subjname.name else: subjtitle = titleattr args = self.get_args(schema) if args: from edb.edgeql import parser as qlparser from edb.edgeql import utils as qlutils args_ql: List[qlast.Base] = [ qlast.Path(steps=[qlast.ObjectRef(name=subjtitle)]), ] args_ql.extend(qlparser.parse(arg.text) for arg in args) constr_base: Constraint = schema.get(self.get_name(schema), type=type(self)) index_parameters = qlutils.index_parameters( args_ql, parameters=constr_base.get_params(schema), schema=schema, ) expr = constr_base.get_field_value(schema, 'expr') expr_ql = qlparser.parse(expr.text) qlutils.inline_parameters(expr_ql, index_parameters) args_map = { name: edgeql.generate_source(val, pretty=False) for name, val in index_parameters.items() } else: args_map = {'__subject__': subjtitle} assert errmsg is not None formatted = errmsg.format(**args_map) return formatted
def declare_view_from_schema( viewcls: s_obj.Object, *, ctx: context.ContextLevel) -> irast.Set: vc = ctx.env.schema_view_cache.get(viewcls) if vc is not None: return vc with ctx.detached() as subctx: subctx.expr_exposed = False view_expr = qlparser.parse(viewcls.get_expr(ctx.env.schema).text) viewcls_name = viewcls.get_name(ctx.env.schema) view_set = declare_view(view_expr, alias=viewcls_name, fully_detached=True, ctx=subctx) # The view path id _itself_ should not be in the nested namespace. view_set.path_id = view_set.path_id.replace_namespace( ctx.path_id_namespace) vc = subctx.aliased_views[viewcls_name] ctx.env.schema_view_cache[viewcls] = vc ctx.source_map.update(subctx.source_map) ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name] ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc ctx.view_sets[vc] = subctx.view_sets[vc] return vc
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema) expected_cardinality = qltypes.Cardinality( textwrap.dedent(expected).strip(' \n')) self.assertEqual(ir.cardinality, expected_cardinality, 'unexpected cardinality:\n' + source)
def trace_Function( node: qlast.CreateFunction, *, ctx: DepTraceContext, ) -> None: # We also need to add all the signature types as dependencies # to make sure that DDL linearization of SDL will define the types # before the function. deps: List[Dependency] = [] deps.extend(TypeDependency(texpr=param.type) for param in node.params) deps.append(TypeDependency(texpr=node.returning)) params = {} for param in node.params: assert isinstance(param.type, qlast.TypeName) if not param.type.subtypes: param_t = ctx.get_ref_name(param.type.maintype) params[param.name] = param_t else: params[param.name] = s_name.QualName('std', 'BaseObject') if node.nativecode is not None: deps.append(FunctionDependency(expr=node.nativecode, params=params)) elif (node.code is not None and node.code.language is qlast.Language.EdgeQL and node.code.code): # Need to parse the actual code string and use that as the dependency. fcode = qlparser.parse(node.code.code) assert isinstance(fcode, qlast.Expr) deps.append(FunctionDependency(expr=fcode, params=params)) # XXX: hard_dep_expr is used because it ultimately calls the # _get_hard_deps helper that extracts the proper dependency list # from types. _register_item(node, ctx=ctx, hard_dep_exprs=deps)
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 run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema) # 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 compiled(cls, expr, schema, *, modaliases=None, parent_object_type=None, anchors=None, path_prefix_anchor=None, allow_generic_type_output=False, func_params=None, singletons=None) -> Expression: from edb.edgeql import compiler as qlcompiler qltree = expr.qlast if qltree is None: qltree = qlparser.parse(expr.text) ir = qlcompiler.compile_ast_to_ir( qltree, schema=schema, modaliases=modaliases, anchors=anchors, path_prefix_anchor=path_prefix_anchor, func_params=func_params, parent_object_type=parent_object_type, allow_generic_type_output=allow_generic_type_output, singletons=singletons, ) return cls( text=expr.text, origtext=expr.origtext, refs=so.ObjectSet.create(schema, ir.schema_refs), _qlast=qltree, _irast=ir, )
def trace_Function(node: qlast.CreateFunction, *, ctx: DepTraceContext): # We also need to add all the signature types as dependencies # to make sure that DDL linearization of SDL will define the types # before the function. deps = [param.type for param in node.params] deps.append(node.returning) params = {} for param in node.params: if not param.type.subtypes: param_t = ctx.get_ref_name(param.type.maintype) params[param.name] = param_t else: params[param.name] = 'std::BaseObject' if node.nativecode is not None: deps.append((node.nativecode, params)) elif (node.code is not None and node.code.language is qlast.Language.EdgeQL and node.code.code): # Need to parse the actual code string and use that as the dependency. deps.append((qlparser.parse(node.code.code), params)) # XXX: hard_dep_expr is used because it ultimately calls the # _get_hard_deps helper that extracts the proper dependency list # from types. _register_item(node, ctx=ctx, hard_dep_exprs=deps)
def _create_begin(self, schema, context): from edb.edgeql import parser as edgeql_parser from edb.edgeql import utils as edgeql_utils parent_ctx = context.get(sd.CommandContextToken) subject = schema.get(parent_ctx.op.classname) expr = self.get_attribute_value('expr') if expr.qlast is not None: tree = expr.qlast else: tree = edgeql_parser.parse(expr.text, context.modaliases) _, _, qlexpr = edgeql_utils.normalize_tree( tree, schema, modaliases=context.modaliases, anchors={qlast.Subject: subject}, inline_anchors=True, ) self.set_attribute_value('expr', s_expr.Expression(text=qlexpr)) return sd.CreateObject._create_begin( self, schema, context)
def ptr_default_to_col_default(schema, ptr, expr): try: # NOTE: This code currently will only be invoked for scalars. # Blindly cast the default expression into the ptr target # type, validation of the expression type is not the concern # of this function. eql = ql_parser.parse(expr.text) eql = ql_astutils.ensure_qlstmt( qlast.TypeCast( type=ql_astutils.type_to_ql_typeref( ptr.get_target(schema), schema=schema), expr=eql, ) ) ir = ql_compiler.compile_ast_to_ir(eql, schema) except errors.SchemaError: # Reference errors mean that is is a non-constant default # referring to a not-yet-existing objects. return None if not ir_utils.is_const(ir): return None sql_expr = compiler.compile_ir_to_sql_tree(ir, singleton_mode=True) sql_text = codegen.SQLSourceGenerator.to_source(sql_expr) return sql_text
def compile_to_ir(expr, schema, *, anchors=None, path_prefix_anchor=None, security_context=None, modaliases=None, implicit_id_in_shapes=False, implicit_tid_in_shapes=False): """Compile given EdgeQL statement into EdgeDB IR.""" if debug.flags.edgeql_compile: debug.header('EdgeQL TEXT') debug.print(expr) tree = ql_parser.parse(expr, modaliases) return compile_ast_to_ir(tree, schema, anchors=anchors, path_prefix_anchor=path_prefix_anchor, security_context=security_context, modaliases=modaliases, implicit_id_in_shapes=implicit_id_in_shapes, implicit_tid_in_shapes=implicit_tid_in_shapes)
def declare_view_from_schema(viewcls: s_types.Type, *, ctx: context.ContextLevel) -> s_types.Type: vc = ctx.env.schema_view_cache.get(viewcls) if vc is not None: return vc with ctx.detached() as subctx: subctx.expr_exposed = False view_expr = viewcls.get_expr(ctx.env.schema) assert view_expr is not None view_ql = qlparser.parse(view_expr.text) viewcls_name = viewcls.get_name(ctx.env.schema) assert isinstance(view_ql, qlast.Expr), 'expected qlast.Expr' view_set = declare_view(view_ql, alias=viewcls_name, fully_detached=True, ctx=subctx) # The view path id _itself_ should not be in the nested namespace. view_set.path_id = view_set.path_id.replace_namespace( ctx.path_id_namespace) vc = subctx.aliased_views[viewcls_name] assert vc is not None ctx.env.schema_view_cache[viewcls] = vc ctx.source_map.update(subctx.source_map) ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name] ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc ctx.view_sets[vc] = subctx.view_sets[vc] return vc
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema) 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 = self.UNION_NAME_RE.sub( '@SID@', 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_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 get_access_policy_filters( self, schema: s_schema.Schema, ) -> Optional[s_expr.ExpressionList]: if (self.get_name(schema).module == 'schema' and self.issubclass( schema, schema.get('schema::Object', type=ObjectType))): return s_expr.ExpressionList([ s_expr.Expression.from_ast( qlparser.parse('NOT .internal'), schema=schema, modaliases={}, ) ]) else: return None
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 _normalize_constraint_expr(cls, schema, module_aliases, expr, subject): from edb.edgeql import compiler as qlcompiler from edb.edgeql import parser as qlparser if isinstance(expr, str): qltree = qlparser.parse(expr, module_aliases) else: qltree = expr ir = qlcompiler.compile_ast_to_ir( qltree, schema, modaliases=module_aliases, anchors={qlast.Subject: subject}, ) return qltree, ir
def declare_view_from_schema(viewcls: s_types.Type, *, ctx: context.ContextLevel) -> s_types.Type: vc = ctx.env.schema_view_cache.get(viewcls) if vc is not None: return vc with ctx.detached() as subctx: subctx.expr_exposed = False view_expr = viewcls.get_expr(ctx.env.schema) assert view_expr is not None view_ql = qlparser.parse(view_expr.text) viewcls_name = viewcls.get_name(ctx.env.schema) assert isinstance(view_ql, qlast.Expr), 'expected qlast.Expr' view_set = declare_view(view_ql, alias=viewcls_name, fully_detached=True, ctx=subctx) # The view path id _itself_ should not be in the nested namespace. view_set.path_id = view_set.path_id.replace_namespace( ctx.path_id_namespace) vc = subctx.aliased_views[viewcls_name] assert vc is not None if not ctx.in_temp_scope: ctx.env.schema_view_cache[viewcls] = vc ctx.source_map.update(subctx.source_map) ctx.aliased_views[viewcls_name] = subctx.aliased_views[viewcls_name] ctx.view_nodes[vc.get_name(ctx.env.schema)] = vc ctx.view_sets[vc] = subctx.view_sets[vc] # XXX: The current cardinality inference machine does not look # into unreferenced expression parts, which includes computables # that may be declared on an alias that another alias is referencing, # leaving Unknown cardinalities in place. To fix this, copy # cardinalities for computed pointers from the alias object in the # schema. view_type = setgen.get_set_type(view_set, ctx=subctx) if isinstance(view_type, s_objtypes.ObjectType): assert isinstance(viewcls, s_objtypes.ObjectType) _fixup_cardinalities( view_type, viewcls, ctx=ctx, ) return vc
def run_test(self, *, source, spec, expected): qltree = qlparser.parse(source) ir = compiler.compile_ast_to_ir(qltree, self.schema) path_scope = self.UUID_RE.sub( '@SID@', textwrap.indent(ir.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 _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> sd.Command: cmd = super()._cmd_tree_from_ast(schema, astnode, context) assert isinstance(astnode, qlast.CreateFunction) if astnode.code is not None: cmd.set_attribute_value( 'language', astnode.code.language, ) if astnode.code.language is qlast.Language.EdgeQL: if astnode.nativecode is not None: nativecode_expr = astnode.nativecode else: nativecode_expr = qlparser.parse(astnode.code.code) nativecode = expr.Expression.from_ast( nativecode_expr, schema, context.modaliases, ) cmd.set_attribute_value( 'nativecode', nativecode, ) elif astnode.code.from_function is not None: cmd.set_attribute_value( 'from_function', astnode.code.from_function ) else: cmd.set_attribute_value( 'code', astnode.code.code, ) return cmd
def _cmd_tree_from_ast( cls, schema: s_schema.Schema, astnode: qlast.DDLOperation, context: sd.CommandContext, ) -> sd.Command: cmd = super()._cmd_tree_from_ast(schema, astnode, context) assert isinstance(astnode, qlast.AlterFunction) if astnode.code is not None: if ( astnode.code.language is not qlast.Language.EdgeQL or astnode.code.from_function is not None or astnode.code.from_expr ): raise errors.EdgeQLSyntaxError( 'altering function code is only supported for ' 'pure EdgeQL functions', context=astnode.context ) nativecode_expr: Optional[qlast.Expr] = None if astnode.nativecode is not None: nativecode_expr = astnode.nativecode elif astnode.code.code is not None: nativecode_expr = qlparser.parse(astnode.code.code) if nativecode_expr is not None: nativecode = expr.Expression.from_ast( nativecode_expr, schema, context.modaliases, ) cmd.set_attribute_value( 'nativecode', nativecode, ) return cmd
def _assert_normalize_expr(self, text, expected, expected_const_type=None, *, anchors=None, inline_anchors=False): edgeql_tree = eql_parser.parse(text) schema = self.__class__.schema ir, _, normalized = eql_utils.normalize_tree( edgeql_tree, schema, anchors=anchors, inline_anchors=inline_anchors) self.assertEqual( textwrap.dedent(normalized).strip(), textwrap.dedent(expected).strip()) if expected_const_type is not None: self.assertEqual( schema.get_by_id(ir.expr.typeref.id).get_displayname(schema), expected_const_type)
def _normalize_constraint_expr(cls, schema, module_aliases, expr, subject, *, inline_anchors=False): from edb.edgeql import parser as edgeql_parser from edb.edgeql import utils as edgeql_utils if isinstance(expr, str): tree = edgeql_parser.parse(expr, module_aliases) else: tree = expr ir, edgeql_tree, _ = edgeql_utils.normalize_tree( tree, schema, modaliases=module_aliases, anchors={qlast.Subject: subject}, inline_anchors=inline_anchors) return edgeql_tree.result, ir
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 computable_ptr_set( rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, srcctx: Optional[parsing.ParserContext] = None, ctx: context.ContextLevel, ) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use the parent of derived ptrcls. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set(source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: source_rptrref = source_set.rptr.ptrref if source_rptrref.base_ptr is not None: source_rptrref = source_rptrref.base_ptr source_set.rptr = irast.Pointer( source=source_set.rptr.source, target=source_set, ptrref=source_rptrref, direction=source_set.rptr.direction, ) qlctx: Optional[context.ContextLevel] inner_source_path_id: Optional[irast.PathId] try: comp_info = ctx.source_map[ptrcls] qlexpr = comp_info.qlexpr assert isinstance(comp_info.context, context.ContextLevel) qlctx = comp_info.context inner_source_path_id = comp_info.path_id path_id_ns = comp_info.path_id_ns except KeyError: comp_expr = ptrcls.get_expr(ctx.env.schema) schema_qlexpr: Optional[qlast.Expr] = None if comp_expr is None and ctx.env.options.apply_query_rewrites: schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema) if schema_deflt is not None: assert isinstance(ptrcls, s_pointers.Pointer) ptrcls_n = ptrcls.get_shortname(ctx.env.schema).name schema_qlexpr = qlast.BinOp( left=qlast.Path(steps=[ qlast.Source(), qlast.Ptr( ptr=qlast.ObjectRef(name=ptrcls_n), direction=s_pointers.PointerDirection.Outbound, type=('property' if ptrcls.is_link_property( ctx.env.schema) else None)) ], ), right=qlparser.parse_fragment(schema_deflt), op='??', ) if schema_qlexpr is None: if comp_expr is None: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise errors.InternalServerError( f'{ptrcls_sn!r} is not a computable pointer') comp_qlexpr = qlparser.parse(comp_expr.text) assert isinstance(comp_qlexpr, qlast.Expr), 'expected qlast.Expr' schema_qlexpr = comp_qlexpr # NOTE: Validation of the expression type is not the concern # of this function. For any non-object pointer target type, # the default expression must be assignment-cast into that # type. target_scls = ptrcls.get_target(ctx.env.schema) assert target_scls is not None if not target_scls.is_object_type(): schema_qlexpr = qlast.TypeCast( type=typegen.type_to_ql_typeref(target_scls, ctx=ctx), expr=schema_qlexpr, ) qlexpr = astutils.ensure_qlstmt(schema_qlexpr) qlctx = None inner_source_path_id = None path_id_ns = None newctx: Callable[[], ContextManager[context.ContextLevel]] if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx(rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: src_path = rptr.target.path_id.src_path() assert src_path is not None source_path_id = src_path result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, ns=ctx.path_id_namespace, ctx=ctx, ) result_stype = ptrcls.get_target(ctx.env.schema) base_object = ctx.env.schema.get('std::BaseObject', type=s_types.Type) with newctx() as subctx: subctx.disable_shadowing.add(ptrcls) if result_stype != base_object: subctx.view_scls = result_stype subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) # type: ignore subctx.anchors[qlast.Source().name] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set # On a mutation, make the expr_exposed. This corresponds with # a similar check on is_mutation in _normalize_view_ptr_expr. if (source_scls.get_expr_type(ctx.env.schema) != s_types.ExprType.Select): subctx.expr_exposed = True if isinstance(qlexpr, qlast.Statement): subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=unnest_fence, iterator_target=True, ) comp_ir_set = ensure_set(dispatch.compile(qlexpr, ctx=subctx), ctx=subctx) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, context=srcctx, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set
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 computable_ptr_set(rptr: irast.Pointer, *, unnest_fence: bool = False, same_computable_scope: bool = False, ctx: context.ContextLevel) -> irast.Set: """Return ir.Set for a pointer defined as a computable.""" ptrcls = irtyputils.ptrcls_from_ptrref(rptr.ptrref, schema=ctx.env.schema) source_set = rptr.source source_scls = get_set_type(source_set, ctx=ctx) # process_view() may generate computable pointer expressions # in the form "self.linkname". To prevent infinite recursion, # self must resolve to the parent type of the view NOT the view # type itself. Similarly, when resolving computable link properties # make sure that we use ptrcls.derived_from. if source_scls.is_view(ctx.env.schema): source_set_stype = source_scls.peel_view(ctx.env.schema) source_set = new_set_from_set(source_set, stype=source_set_stype, preserve_scope_ns=True, ctx=ctx) source_set.shape = [] if source_set.rptr is not None: schema = ctx.env.schema source_rptrref = source_set.rptr.ptrref source_rptrcls = irtyputils.ptrcls_from_ptrref(source_rptrref, schema=schema) derived_from = source_rptrcls.get_derived_from(schema) if (derived_from is not None and not derived_from.generic(schema) and derived_from.get_derived_from(schema) is not None and ptrcls.is_link_property(schema)): source_set.rptr.ptrref = irtyputils.ptrref_from_ptrcls( source_ref=source_rptrref.dir_source, target_ref=source_rptrref.dir_target, direction=source_rptrref.direction, parent_ptr=source_rptrref.parent_ptr, ptrcls=derived_from, schema=schema, ) stmtctx.ensure_ptrref_cardinality(derived_from, source_set.rptr.ptrref, ctx=ctx) try: qlexpr, qlctx, inner_source_path_id, path_id_ns = \ ctx.source_map[ptrcls] except KeyError: ptrcls_default = ptrcls.get_default(ctx.env.schema) if not ptrcls_default: ptrcls_sn = ptrcls.get_shortname(ctx.env.schema) raise ValueError(f'{ptrcls_sn!r} is not a computable pointer') qlexpr = astutils.ensure_qlstmt(qlparser.parse(ptrcls_default.text)) qlctx = None inner_source_path_id = None path_id_ns = None if qlctx is None: # Schema-level computable, completely detached context newctx = ctx.detached else: newctx = _get_computable_ctx(rptr=rptr, source=source_set, source_scls=source_scls, inner_source_path_id=inner_source_path_id, path_id_ns=path_id_ns, same_scope=same_computable_scope, qlctx=qlctx, ctx=ctx) if ptrcls.is_link_property(ctx.env.schema): source_path_id = rptr.source.path_id.ptr_path() else: source_path_id = rptr.target.path_id.src_path() result_stype = ptrcls.get_target(ctx.env.schema) result_path_id = pathctx.extend_path_id( source_path_id, ptrcls=ptrcls, direction=s_pointers.PointerDirection.Outbound, target=result_stype, ns=ctx.path_id_namespace, ctx=ctx) with newctx() as subctx: subctx.view_scls = result_stype subctx.view_rptr = context.ViewRPtr(source_scls, ptrcls=ptrcls, rptr=rptr) subctx.anchors[qlast.Source] = source_set subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema) subctx.partial_path_prefix = source_set if isinstance(qlexpr, qlast.Statement) and unnest_fence: subctx.stmt_metadata[qlexpr] = context.StatementMetadata( is_unnest_fence=True) comp_ir_set = dispatch.compile(qlexpr, ctx=subctx) comp_ir_set_copy = new_set_from_set(comp_ir_set, ctx=ctx) pending_cardinality = ctx.pending_cardinality.get(ptrcls) if pending_cardinality is not None and not pending_cardinality.from_parent: stmtctx.get_pointer_cardinality_later( ptrcls=ptrcls, irexpr=comp_ir_set_copy, specified_card=pending_cardinality.specified_cardinality, source_ctx=pending_cardinality.source_ctx, ctx=ctx) def _check_cardinality(ctx): if ptrcls.singular(ctx.env.schema): stmtctx.enforce_singleton_now(comp_ir_set_copy, ctx=ctx) stmtctx.at_stmt_fini(_check_cardinality, ctx=ctx) comp_ir_set = new_set_from_set(comp_ir_set, path_id=result_path_id, rptr=rptr, ctx=ctx) rptr.target = comp_ir_set return comp_ir_set