示例#1
0
    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
示例#2
0
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
示例#3
0
 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)
示例#4
0
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)
示例#7
0
    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,
        )
示例#8
0
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)
示例#9
0
    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)
示例#10
0
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
示例#11
0
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)
示例#12
0
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}')
示例#14
0
 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)
示例#17
0
 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()}')
示例#19
0
    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
示例#20
0
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
示例#21
0
    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}')
示例#22
0
    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
示例#23
0
    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
示例#24
0
    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)
示例#25
0
    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
示例#26
0
    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)))
示例#27
0
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
示例#28
0
    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
示例#29
0
    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)))
示例#30
0
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