Exemplo n.º 1
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
        track_schema_ref_exprs: bool = False,
    ) -> s_expr.Expression:
        if field.name in {'expr', 'condition'}:
            parent_ctx = self.get_referrer_context_or_die(context)
            source = parent_ctx.op.get_object(schema, context)
            parent_vname = source.get_verbosename(schema)
            pol_name = self.get_verbosename(parent=parent_vname)
            in_ddl_context_name = pol_name

            assert isinstance(source, s_types.Type)

            return type(value).compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    schema_object_context=self.get_schema_metaclass(),
                    anchors={qlast.Subject().name: source},
                    path_prefix_anchor=qlast.Subject().name,
                    singletons=frozenset({source}),
                    apply_query_rewrites=not context.stdmode,
                    track_schema_ref_exprs=track_schema_ref_exprs,
                    in_ddl_context_name=in_ddl_context_name,
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value,
                                              track_schema_ref_exprs)
Exemplo n.º 2
0
    async def compile_graphql(
        self,
        dbver: bytes,
        gql: str,
        tokens: Optional[List[Tuple[gql_lexer.TokenKind, int, int, int, int,
                                    str]]],
        substitutions: Optional[Dict[str, Tuple[str, int, int]]],
        operation_name: str = None,
        variables: Optional[Mapping[str, object]] = None,
    ) -> CompiledOperation:

        db = await self._get_database(dbver)

        if tokens is None:
            ast = graphql.parse_text(gql)
        else:
            ast = graphql.parse_tokens(gql, tokens)
        op = graphql.translate_ast(
            db.gqlcore,  # type: ignore[attr-defined]
            ast,
            variables=variables,
            substitutions=substitutions,
            operation_name=operation_name)

        ir = qlcompiler.compile_ast_to_ir(
            op.edgeql_ast,
            schema=db.schema,
            options=qlcompiler.CompilerOptions(
                json_parameters=True,
                allow_top_level_shape_dml=True,
            ),
        )

        if ir.cardinality.is_multi():
            raise errors.ResultCardinalityMismatchError(
                f'compiled GrqphQL query has cardinality {ir.cardinality}, '
                f'expected ONE')

        sql_text, argmap = pg_compiler.compile_ir_to_sql(
            ir,
            pretty=bool(debug.flags.edgeql_compile),
            expected_cardinality_one=True,
            output_format=pg_compiler.OutputFormat.JSON)

        args: List[Optional[str]] = [None] * len(argmap)
        for argname, param in argmap.items():
            args[param.index - 1] = argname

        sql_bytes = sql_text.encode()
        sql_hash = self._hash_sql(sql_bytes)

        return CompiledOperation(
            sql=sql_bytes,
            sql_hash=sql_hash,
            sql_args=args,  # type: ignore[arg-type]  # XXX: optional bug?
            dbver=dbver,
            cacheable=op.cacheable,
            cache_deps_vars=op.cache_deps_vars,
            variables=op.variables_desc,
        )
Exemplo n.º 3
0
    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}')
Exemplo n.º 4
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
        track_schema_ref_exprs: bool = False,
    ) -> s_expr.Expression:
        if field.name in {'default', 'expr'}:
            ptr_name = self.get_verbosename()
            in_ddl_context_name = None
            if field.name == 'expr':
                in_ddl_context_name = f'computed {ptr_name}'

            return type(value).compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    schema_object_context=self.get_schema_metaclass(),
                    apply_query_rewrites=not context.stdmode,
                    track_schema_ref_exprs=track_schema_ref_exprs,
                    in_ddl_context_name=in_ddl_context_name,
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value,
                                              track_schema_ref_exprs)
Exemplo n.º 5
0
    def _compile_alias_expr(
        self,
        expr: qlast.Base,
        classname: sn.SchemaName,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> irast.Statement:
        cached: Optional[irast.Statement] = (context.get_cached(
            (expr, classname)))
        if cached is not None:
            return cached

        if not isinstance(expr, qlast.Statement):
            expr = qlast.SelectQuery(result=expr)

        existing = schema.get(classname, type=s_types.Type, default=None)
        if existing is not None:
            drop_cmd = existing.init_delta_command(schema, sd.DeleteObject)
            with context.suspend_dep_verification():
                schema = drop_cmd.apply(schema, context)

        ir = qlcompiler.compile_ast_to_ir(
            expr,
            schema,
            options=qlcompiler.CompilerOptions(
                derived_target_module=classname.module,
                result_view_name=classname,
                modaliases=context.modaliases,
                schema_view_mode=True,
            ),
        )

        context.cache_value((expr, classname), ir)

        return ir  # type: ignore
Exemplo n.º 6
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
        track_schema_ref_exprs: bool = False,
    ) -> s_expr.Expression:
        from . import objtypes as s_objtypes

        singletons: List[s_types.Type]
        if field.name == 'expr':
            # type ignore below, for the class is used as mixin
            parent_ctx = context.get_ancestor(
                IndexSourceCommandContext,  # type: ignore
                self)
            assert parent_ctx is not None
            assert isinstance(parent_ctx.op, sd.ObjectCommand)
            subject = parent_ctx.op.get_object(schema, context)

            if isinstance(subject, s_abc.Pointer):
                singletons = []
                path_prefix_anchor = None
            else:
                assert isinstance(subject, s_objtypes.ObjectType)
                singletons = [subject]
                path_prefix_anchor = qlast.Subject().name

            expr = type(value).compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    schema_object_context=self.get_schema_metaclass(),
                    anchors={qlast.Subject().name: subject},
                    path_prefix_anchor=path_prefix_anchor,
                    singletons=frozenset(singletons),
                    apply_query_rewrites=not context.stdmode,
                    track_schema_ref_exprs=track_schema_ref_exprs,
                ),
            )

            # Check that the inferred cardinality is no more than 1
            from edb.ir import ast as ir_ast
            assert isinstance(expr.irast, ir_ast.Statement)
            if expr.irast.cardinality.is_multi():
                raise errors.ResultCardinalityMismatchError(
                    f'possibly more than one element returned by '
                    f'the index expression where only singletons '
                    f'are allowed')

            return expr
        else:
            return super().compile_expr_field(schema, context, field, value,
                                              track_schema_ref_exprs)
Exemplo n.º 7
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)
Exemplo n.º 10
0
    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()}')
Exemplo n.º 11
0
    def from_ast(
        cls,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        num: int,
        astnode: qlast.FuncParam,
    ) -> ParameterDesc:

        paramd = None
        if astnode.default is not None:
            defexpr = expr.Expression.from_ast(
                astnode.default, schema, modaliases, as_fragment=True)
            paramd = expr.Expression.compiled(
                defexpr,
                schema,
                as_fragment=True,
                options=qlcompiler.CompilerOptions(
                    modaliases=modaliases,
                )
            )

        paramt_ast = astnode.type

        if astnode.kind is ft.ParameterKind.VARIADIC:
            paramt_ast = qlast.TypeName(
                maintype=qlast.ObjectRef(
                    name='array',
                ),
                subtypes=[paramt_ast],
            )

        assert isinstance(paramt_ast, qlast.TypeName)
        paramt = utils.ast_to_type_shell(
            paramt_ast,
            modaliases=modaliases,
            schema=schema,
        )

        return cls(
            num=num,
            name=astnode.name,
            type=paramt,
            typemod=astnode.typemod,
            kind=astnode.kind,
            default=paramd
        )
Exemplo n.º 12
0
    def _compile_alias_expr(
        self,
        expr: qlast.Base,
        classname: sn.QualName,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> irast.Statement:
        cached: Optional[irast.Statement] = (
            context.get_cached((expr, classname)))
        if cached is not None:
            return cached

        if not isinstance(expr, qlast.Statement):
            expr = qlast.SelectQuery(result=expr)

        existing = schema.get(classname, type=s_types.Type, default=None)
        if existing is not None:
            drop_cmd = existing.init_delta_command(schema, sd.DeleteObject)
            with context.suspend_dep_verification():
                schema = drop_cmd.apply(schema, context)

        ir = qlcompiler.compile_ast_to_ir(
            expr,
            schema,
            options=qlcompiler.CompilerOptions(
                derived_target_module=classname.module,
                result_view_name=classname,
                modaliases=context.modaliases,
                schema_view_mode=True,
                in_ddl_context_name='alias definition',
            ),
        )

        if ir.volatility == qltypes.Volatility.Volatile:
            srcctx = self.get_attribute_source_context('expr')
            raise errors.SchemaDefinitionError(
                f'volatile functions are not permitted in schema-defined '
                f'computables',
                context=srcctx
            )

        context.cache_value((expr, classname), ir)

        return ir  # type: ignore
    def run_test(self, *, source, spec, expected):
        qltree = qlparser.parse(source)
        ir = compiler.compile_ast_to_ir(
            qltree,
            self.schema,
            options=compiler.CompilerOptions(
                modaliases={None: 'default'},
            ),
        )

        if not expected:
            return

        # The expected cardinality is either given for the whole query
        # (by default) or for a specific element of the top-level
        # shape. In case of the specific element the name of the shape
        # element must be given followed by ":" and then the
        # cardinality.
        exp = textwrap.dedent(expected).strip(' \n').split(':')

        if len(exp) == 1:
            field = None
            expected_cardinality = qltypes.Cardinality(exp[0])
        elif len(exp) == 2:
            field = exp[0].strip()
            expected_cardinality = qltypes.Cardinality(exp[1].strip())
        else:
            raise ValueError(
                f'unrecognized expected specification: {expected!r}')

        if field is not None:
            shape = ir.expr.expr.result.shape
            for el, _ in shape:
                if str(el.path_id.rptr_name()).endswith(field):
                    card = el.rptr.ptrref.out_cardinality
                    self.assertEqual(card, expected_cardinality,
                                     'unexpected cardinality:\n' + source)
                    break
            else:
                raise AssertionError(f'shape field not found: {field!r}')

        else:
            self.assertEqual(ir.cardinality, expected_cardinality,
                             'unexpected cardinality:\n' + source)
Exemplo n.º 14
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
    ) -> s_expr.Expression:
        from . import sources as s_sources

        if field.name in {'default', 'expr'}:
            singletons: List[s_types.Type] = []
            path_prefix_anchor = None
            anchors: Dict[str, Any] = {}

            if field.name == 'expr':
                # type ignore below, because the class is used as mixin
                parent_ctx = context.get_ancestor(
                    s_sources.SourceCommandContext,  # type: ignore
                    self
                )
                assert parent_ctx is not None
                assert isinstance(parent_ctx.op, sd.ObjectCommand)
                source_name = parent_ctx.op.classname
                source = schema.get(source_name, default=None)
                anchors[qlast.Source().name] = source
                if not isinstance(source, Pointer):
                    assert source is not None
                    singletons = [source]
                    path_prefix_anchor = qlast.Source().name

            return type(value).compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    schema_object_context=self.get_schema_metaclass(),
                    anchors=anchors,
                    path_prefix_anchor=path_prefix_anchor,
                    singletons=frozenset(singletons),
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value)
Exemplo n.º 15
0
 def compile_expr_field(
     self,
     schema: s_schema.Schema,
     context: sd.CommandContext,
     field: so.Field[Any],
     value: expr.Expression,
 ) -> expr.Expression:
     if field.name == 'initial_value':
         return type(value).compiled(
             value,
             schema=schema,
             options=qlcompiler.CompilerOptions(
                 allow_generic_type_output=True,
                 schema_object_context=self.get_schema_metaclass(),
             ),
         )
     elif field.name == 'nativecode':
         return self.compile_function(schema, context, value)
     else:
         return super().compile_expr_field(schema, context, field, value)
Exemplo n.º 16
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
    ) -> s_expr.Expression:
        from . import objtypes as s_objtypes

        singletons: List[s_types.Type]
        if field.name == 'expr':
            # type ignore below, for the class is used as mixin
            parent_ctx = context.get_ancestor(
                IndexSourceCommandContext,  # type: ignore
                self)
            assert parent_ctx is not None
            subject_name = parent_ctx.op.classname
            subject = schema.get(subject_name, default=None)

            if isinstance(subject, s_abc.Pointer):
                singletons = []
                path_prefix_anchor = None
            else:
                assert isinstance(subject, s_objtypes.ObjectType)
                singletons = [subject]
                path_prefix_anchor = qlast.Subject().name

            return type(value).compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    schema_object_context=self.get_schema_metaclass(),
                    anchors={qlast.Subject().name: subject},
                    path_prefix_anchor=path_prefix_anchor,
                    singletons=frozenset(singletons),
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value)
Exemplo n.º 17
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
    ) -> s_expr.Expression:

        if field.name in ('expr', 'subjectexpr'):
            if not isinstance(self, CreateConstraint):
                raise TypeError("ALTER-ing constraint expressions "
                                "is not supported")
            params = self._get_params(schema, context)

            anchors: Dict[str, Any] = {}
            param_anchors = s_func.get_params_symtable(
                params,
                schema,
                inlined_defaults=False,
            )
            anchors.update(param_anchors)
            referrer_ctx = self.get_referrer_context(context)

            if referrer_ctx is not None:
                assert isinstance(referrer_ctx.op, sd.ObjectCommand)
                anchors['__subject__'] = referrer_ctx.op.scls

            return s_expr.Expression.compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    anchors=anchors,
                    func_params=params,
                    allow_generic_type_output=True,
                    schema_object_context=self.get_schema_metaclass(),
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value)
Exemplo n.º 18
0
 def compile_expr_field(
     self,
     schema: s_schema.Schema,
     context: sd.CommandContext,
     field: so.Field[Any],
     value: s_expr.Expression,
     track_schema_ref_exprs: bool = False,
 ) -> s_expr.Expression:
     assert field.name == 'expr'
     classname = sn.shortname_from_fullname(self.classname)
     assert isinstance(classname, sn.QualName), \
         "expected qualified name"
     return type(value).compiled(
         value,
         schema=schema,
         options=qlcompiler.CompilerOptions(
             derived_target_module=classname.module,
             modaliases=context.modaliases,
             in_ddl_context_name='alias definition',
             track_schema_ref_exprs=track_schema_ref_exprs,
         ),
     )
Exemplo n.º 19
0
    def _parse_computable(
        self,
        expr: qlast.Base,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]:
        from edb.ir import ast as irast
        from edb.ir import typeutils as irtyputils
        from edb.schema import objtypes as s_objtypes

        # "source" attribute is set automatically as a refdict back-attr
        parent_ctx = self.get_referrer_context(context)
        assert parent_ctx is not None
        source_name = parent_ctx.op.classname

        source = schema.get(source_name, type=s_objtypes.ObjectType)
        expression = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr, schema, context.modaliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=context.modaliases,
                anchors={qlast.Source().name: source},
                path_prefix_anchor=qlast.Source().name,
                singletons=frozenset([source]),
            ),
        )

        assert isinstance(expression.irast, irast.Statement)
        base = None
        target = expression.irast.stype

        result_expr = expression.irast.expr.expr

        if (isinstance(result_expr, irast.SelectStmt)
                and result_expr.result.rptr is not None):
            expr_rptr = result_expr.result.rptr
            while isinstance(expr_rptr, irast.TypeIntersectionPointer):
                expr_rptr = expr_rptr.source.rptr

            is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound)

            if is_ptr_alias:
                new_schema, base = irtyputils.ptrcls_from_ptrref(
                    expr_rptr.ptrref, schema=schema)
                # Only pointers coming from the same source as the
                # alias should be "inherited" (in order to preserve
                # link props). Random paths coming from other sources
                # get treated same as any other arbitrary expression
                # in a computable.
                if base.get_source(new_schema) != source:
                    base = None
                else:
                    schema = new_schema

        self.set_attribute_value('expr', expression)
        required, card = expression.irast.cardinality.to_schema_value()
        spec_required = self.get_attribute_value('required')
        spec_card = self.get_attribute_value('cardinality')

        if spec_required and not required:
            ptr_name = sn.shortname_from_fullname(
                self.get_attribute_value('name')).name
            srcctx = self.get_attribute_source_context('target')
            raise errors.SchemaDefinitionError(
                f'possibly an empty set returned by an '
                f'expression for the computable '
                f'{ptr_name!r} '
                f"declared as 'required'",
                context=srcctx)

        if (spec_card in {None, qltypes.SchemaCardinality.ONE}
                and card is not qltypes.SchemaCardinality.ONE):
            ptr_name = sn.shortname_from_fullname(
                self.get_attribute_value('name')).name
            srcctx = self.get_attribute_source_context('target')
            raise errors.SchemaDefinitionError(
                f'possibly more than one element returned by an '
                f'expression for the computable '
                f'{ptr_name!r} '
                f"declared as 'single'",
                context=srcctx)

        if spec_card is None:
            self.set_attribute_value('cardinality', card)

        if not spec_required:
            self.set_attribute_value('required', required)

        self.set_attribute_value('computable', True)

        return schema, target, base
Exemplo n.º 20
0
def compile_graphql(
    std_schema: s_schema.FlatSchema,
    user_schema: s_schema.FlatSchema,
    global_schema: s_schema.FlatSchema,
    database_config: Mapping[str, Any],
    system_config: Mapping[str, Any],
    gql: str,
    tokens: Optional[List[Tuple[gql_lexer.TokenKind, int, int, int, int,
                                str]]],
    substitutions: Optional[Dict[str, Tuple[str, int, int]]],
    operation_name: str = None,
    variables: Optional[Mapping[str, object]] = None,
) -> CompiledOperation:
    if tokens is None:
        ast = graphql.parse_text(gql)
    else:
        ast = graphql.parse_tokens(gql, tokens)

    gqlcore = _get_gqlcore(std_schema, user_schema, global_schema)

    op = graphql.translate_ast(gqlcore,
                               ast,
                               variables=variables,
                               substitutions=substitutions,
                               operation_name=operation_name)

    ir = qlcompiler.compile_ast_to_ir(
        op.edgeql_ast,
        schema=s_schema.ChainedSchema(
            std_schema,
            user_schema,
            global_schema,
        ),
        options=qlcompiler.CompilerOptions(
            json_parameters=True,
            allow_top_level_shape_dml=True,
        ),
    )

    if ir.cardinality.is_multi():
        raise errors.ResultCardinalityMismatchError(
            f'compiled GrqphQL query has cardinality {ir.cardinality}, '
            f'expected ONE')

    sql_text, argmap = pg_compiler.compile_ir_to_sql(
        ir,
        pretty=bool(debug.flags.edgeql_compile),
        expected_cardinality_one=True,
        output_format=pg_compiler.OutputFormat.JSON)

    args: List[Optional[str]] = [None] * len(argmap)
    for argname, param in argmap.items():
        args[param.index - 1] = argname

    sql_bytes = sql_text.encode()
    sql_hash = hashlib.sha1(sql_bytes).hexdigest().encode('latin1')

    return CompiledOperation(
        sql=sql_bytes,
        sql_hash=sql_hash,
        sql_args=args,  # type: ignore[arg-type]  # XXX: optional bug?
        cacheable=op.cacheable,
        cache_deps_vars=op.cache_deps_vars,
        variables=op.variables_desc,
    )
Exemplo n.º 21
0
    def compile_expr_field(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        field: so.Field[Any],
        value: s_expr.Expression,
    ) -> s_expr.Expression:

        referrer_ctx = self.get_referrer_context(context)
        if referrer_ctx is not None:
            # Concrete constraint
            if field.name == 'expr':
                # Concrete constraints cannot redefine the base check
                # expressions, and so the only way we should get here
                # is through field inheritance, so check that the
                # value is compiled and move on.
                if not value.is_compiled():
                    mcls = self.get_schema_metaclass()
                    dn = mcls.get_schema_class_displayname()
                    raise errors.InternalServerError(
                        f'uncompiled expression in the {field.name!r} field of'
                        f' {dn} {self.classname!r}')
                return value

            elif field.name in {'subjectexpr', 'finalexpr'}:
                anchors = {'__subject__': referrer_ctx.op.scls}
                return s_expr.Expression.compiled(
                    value,
                    schema=schema,
                    options=qlcompiler.CompilerOptions(
                        modaliases=context.modaliases,
                        anchors=anchors,
                        allow_generic_type_output=True,
                        schema_object_context=self.get_schema_metaclass(),
                    ),
                )

            else:
                return super().compile_expr_field(schema, context, field,
                                                  value)

        elif field.name in ('expr', 'subjectexpr'):
            # Abstract constraint.
            params = self._get_params(schema, context)

            param_anchors = s_func.get_params_symtable(
                params,
                schema,
                inlined_defaults=False,
            )

            return s_expr.Expression.compiled(
                value,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    modaliases=context.modaliases,
                    anchors=param_anchors,
                    func_params=params,
                    allow_generic_type_output=True,
                    schema_object_context=self.get_schema_metaclass(),
                ),
            )
        else:
            return super().compile_expr_field(schema, context, field, value)
Exemplo n.º 22
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)))
Exemplo n.º 23
0
    def schema_constraint_to_backend_constraint(cls, subject, constraint,
                                                schema, context,
                                                source_context):
        assert constraint.get_subject(schema) is not None

        constraint_origin = cls._get_constraint_origin(schema, constraint)
        if constraint_origin != constraint:
            origin_subject = constraint_origin.get_subject(schema)
        else:
            origin_subject = subject

        path_prefix_anchor = (qlast.Subject().name if isinstance(
            subject, s_types.Type) else None)

        ir = qlcompiler.compile_ast_to_ir(
            constraint.get_finalexpr(schema).qlast,
            schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject},
                path_prefix_anchor=path_prefix_anchor,
                apply_query_rewrites=not context.stdmode,
            ),
        )

        terminal_refs = ir_utils.get_longest_paths(ir.expr.expr.result)
        ref_tables = get_ref_storage_info(ir.schema, terminal_refs)

        if len(ref_tables) > 1:
            raise errors.InvalidConstraintDefinitionError(
                f'Constraint {constraint.get_displayname(schema)} on '
                f'{subject.get_displayname(schema)} is not supported '
                f'because it would depend on multiple objects',
                context=source_context,
            )
        elif ref_tables:
            subject_db_name, _ = next(iter(ref_tables.items()))
        else:
            subject_db_name = common.get_backend_name(schema,
                                                      subject,
                                                      catenate=False)

        exclusive_expr_refs = cls._get_exclusive_refs(ir)

        pg_constr_data = {
            'subject_db_name': subject_db_name,
            'expressions': [],
            'origin_expressions': [],
        }

        if constraint_origin != constraint:
            origin_path_prefix_anchor = (qlast.Subject().name if isinstance(
                origin_subject, s_types.Type) else None)
            origin_ir = qlcompiler.compile_ast_to_ir(
                constraint_origin.get_finalexpr(schema).qlast,
                schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: origin_subject},
                    path_prefix_anchor=origin_path_prefix_anchor,
                    apply_query_rewrites=not context.stdmode,
                ),
            )

            origin_terminal_refs = ir_utils.get_longest_paths(
                origin_ir.expr.expr.result)
            origin_ref_tables = get_ref_storage_info(origin_ir.schema,
                                                     origin_terminal_refs)

            if origin_ref_tables:
                origin_subject_db_name, _ = (next(
                    iter(origin_ref_tables.items())))
            else:
                origin_subject_db_name = common.get_backend_name(
                    schema,
                    origin_subject,
                    catenate=False,
                )

            origin_exclusive_expr_refs = cls._get_exclusive_refs(origin_ir)
            pg_constr_data['origin_subject_db_name'] = origin_subject_db_name
        else:
            origin_exclusive_expr_refs = None
            pg_constr_data['origin_subject_db_name'] = subject_db_name

        if exclusive_expr_refs:
            for ref in exclusive_expr_refs:
                exprdata = cls._edgeql_ref_to_pg_constr(
                    subject, origin_subject, ref, schema)
                pg_constr_data['expressions'].append(exprdata)

            if origin_exclusive_expr_refs:
                for ref in origin_exclusive_expr_refs:
                    exprdata = cls._edgeql_ref_to_pg_constr(
                        subject, origin_subject, ref, schema)
                    pg_constr_data['origin_expressions'].append(exprdata)
            else:
                pg_constr_data['origin_expressions'] = (
                    pg_constr_data['expressions'])

            pg_constr_data['scope'] = 'relation'
            pg_constr_data['type'] = 'unique'
        else:
            exprdata = cls._edgeql_ref_to_pg_constr(subject, origin_subject,
                                                    ir, schema)
            pg_constr_data['expressions'].append(exprdata)

            pg_constr_data['scope'] = 'row'
            pg_constr_data['type'] = 'check'

        if isinstance(constraint.get_subject(schema), s_scalars.ScalarType):
            constraint = SchemaDomainConstraint(subject=subject,
                                                constraint=constraint,
                                                pg_constr_data=pg_constr_data,
                                                schema=schema)
        else:
            constraint = SchemaTableConstraint(subject=subject,
                                               constraint=constraint,
                                               pg_constr_data=pg_constr_data,
                                               schema=schema)
        return constraint
Exemplo n.º 24
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,
        subjectexpr_inherited: bool = False,
        sourcectx: Optional[c_parsing.ParserContext] = None,
        args: Any = None,
        **kwargs: Any
    ) -> None:
        from edb.ir import ast as ir_ast
        from edb.ir import utils as ir_utils
        from . import pointers as s_pointers
        from . import links as s_links
        from . import scalars as s_scalars

        bases = self.get_resolved_attribute_value(
            'bases', schema=schema, context=context,
        )
        if not bases:
            bases = self.scls.get_bases(schema)
        constr_base = bases.objects(schema)[0]
        # If we have a concrete base, then we should inherit all of
        # these attrs through the normal inherit_fields() mechanisms,
        # and populating them ourselves will just mess up
        # inherited_fields.
        if not constr_base.generic(schema):
            return

        orig_subjectexpr = subjectexpr
        orig_subject = subject_obj
        base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr')
        if subjectexpr is None:
            subjectexpr = base_subjectexpr
        elif (base_subjectexpr is not None
                and subjectexpr.text != base_subjectexpr.text):
            raise errors.InvalidConstraintDefinitionError(
                f'subjectexpr is already defined for {name}'
            )

        if (isinstance(subject_obj, s_scalars.ScalarType)
                and constr_base.get_is_aggregate(schema)):
            raise errors.InvalidConstraintDefinitionError(
                f'{constr_base.get_verbosename(schema)} may not '
                f'be used on scalar types'
            )

        if subjectexpr is not None:
            subject_ql = subjectexpr.qlast
            subject = subject_ql
        else:
            subject = subject_obj

        expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr')
        if not expr:
            raise errors.InvalidConstraintDefinitionError(
                f'missing constraint expression in {name}')

        # Re-parse instead of using expr.qlast, because we mutate
        # the AST below.
        expr_ql = qlparser.parse(expr.text)

        if not args:
            args = constr_base.get_field_value(schema, 'args')

        attrs = dict(kwargs)
        inherited = dict()
        if orig_subjectexpr is not None:
            attrs['subjectexpr'] = orig_subjectexpr
            inherited['subjectexpr'] = subjectexpr_inherited
        else:
            base_subjectexpr = constr_base.get_subjectexpr(schema)
            if base_subjectexpr is not None:
                attrs['subjectexpr'] = base_subjectexpr
                inherited['subjectexpr'] = True

        errmessage = attrs.get('errmessage')
        if not errmessage:
            errmessage = constr_base.get_errmessage(schema)
            inherited['errmessage'] = True

        attrs['errmessage'] = errmessage

        if subject is not orig_subject:
            # subject has been redefined
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(
                expr_ql, anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.Subject()]),
            ]
            args_ql.extend(arg.qlast for arg in args)
            args_map = qlutils.index_parameters(
                args_ql,
                parameters=constr_base.get_params(schema),
                schema=schema,
            )
            qlutils.inline_parameters(expr_ql, args_map)

        attrs['args'] = args

        assert subject is not None
        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, {}),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject},
                path_prefix_anchor=qlast.Subject().name,
                apply_query_rewrites=not context.stdmode,
            ),
        )

        bool_t = schema.get('std::bool', type=s_scalars.ScalarType)
        assert isinstance(final_expr.irast, ir_ast.Statement)

        expr_type = final_expr.irast.stype
        if not expr_type.issubclass(schema, bool_t):
            raise errors.InvalidConstraintDefinitionError(
                f'{name} constraint expression expected '
                f'to return a bool value, got '
                f'{expr_type.get_verbosename(schema)}',
                context=sourcectx
            )

        if subjectexpr is not None:
            assert isinstance(subject_obj, (s_types.Type, s_pointers.Pointer))
            singletons = frozenset({subject_obj})

            final_subjectexpr = s_expr.Expression.compiled(
                subjectexpr,
                schema=schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: subject},
                    path_prefix_anchor=qlast.Subject().name,
                    singletons=singletons,
                    apply_query_rewrites=not context.stdmode,
                ),
            )
            assert isinstance(final_subjectexpr.irast, ir_ast.Statement)

            refs = ir_utils.get_longest_paths(final_expr.irast)
            has_multi = False
            for ref in refs:
                while ref.rptr:
                    rptr = ref.rptr
                    if rptr.dir_cardinality.is_multi():
                        has_multi = True

                    # We don't need to look further than the subject,
                    # which is always valid. (And which is a singleton
                    # in a constraint expression if it is itself a
                    # singleton, regardless of other parts of the path.)
                    if (
                        isinstance(rptr.ptrref, ir_ast.PointerRef)
                        and rptr.ptrref.id == subject_obj.id
                    ):
                        break

                    if (not isinstance(rptr.ptrref,
                                       ir_ast.TupleIndirectionPointerRef)
                            and rptr.ptrref.source_ptr is None
                            and rptr.source.rptr is not None):
                        if isinstance(subject, s_links.Link):
                            raise errors.InvalidConstraintDefinitionError(
                                "link constraints may not access "
                                "the link target",
                                context=sourcectx
                            )
                        else:
                            raise errors.InvalidConstraintDefinitionError(
                                "constraints cannot contain paths with more "
                                "than one hop",
                                context=sourcectx
                            )

                    ref = rptr.source

            if has_multi and len(refs) > 1:
                raise errors.InvalidConstraintDefinitionError(
                    "cannot reference multiple links or properties in a "
                    "constraint where at least one link or property is MULTI",
                    context=sourcectx
                )

            if has_multi and ir_utils.contains_set_of_op(
                    final_subjectexpr.irast):
                raise errors.InvalidConstraintDefinitionError(
                    "cannot use aggregate functions or operators "
                    "in a non-aggregating constraint",
                    context=sourcectx
                )

        attrs['finalexpr'] = final_expr
        attrs['params'] = constr_base.get_params(schema)
        inherited['params'] = True
        attrs['abstract'] = False

        for k, v in attrs.items():
            self.set_attribute_value(k, v, inherited=bool(inherited.get(k)))
Exemplo n.º 25
0
    def get_concrete_constraint_attrs(
        cls,
        schema: s_schema.Schema,
        subject: Optional[so.Object],
        *,
        name: str,
        subjectexpr: Optional[s_expr.Expression] = None,
        sourcectx: Optional[c_parsing.ParserContext] = None,
        args: Any = None,
        modaliases: Optional[Mapping[Optional[str], str]] = None,
        **kwargs: Any
    ) -> Tuple[Any, Dict[str, Any], Dict[str, bool]]:
        # constr_base, attrs, inherited
        from edb.edgeql import parser as qlparser
        from edb.edgeql import utils as qlutils
        from edb.ir import ast as ir_ast

        constr_base: Constraint = schema.get(name, module_aliases=modaliases)

        module_aliases: Mapping[Optional[str], str] = {}

        orig_subjectexpr = subjectexpr
        orig_subject = subject
        base_subjectexpr = constr_base.get_field_value(schema, 'subjectexpr')
        if subjectexpr is None:
            subjectexpr = base_subjectexpr
        elif (base_subjectexpr is not None
                and subjectexpr.text != base_subjectexpr.text):
            raise errors.InvalidConstraintDefinitionError(
                'subjectexpr is already defined for ' +
                f'{str(name)!r}')

        if subjectexpr is not None:
            subject_ql = subjectexpr.qlast
            if subject_ql is None:
                subject_ql = qlparser.parse(subjectexpr.text, module_aliases)

            subject = subject_ql

        expr: s_expr.Expression = constr_base.get_field_value(schema, 'expr')
        if not expr:
            raise errors.InvalidConstraintDefinitionError(
                f'missing constraint expression in {name!r}')

        expr_ql = qlparser.parse(expr.text, module_aliases)

        if not args:
            args = constr_base.get_field_value(schema, 'args')

        attrs = dict(kwargs)
        inherited = dict()
        if orig_subjectexpr is not None:
            attrs['subjectexpr'] = orig_subjectexpr
        else:
            base_subjectexpr = constr_base.get_subjectexpr(schema)
            if base_subjectexpr is not None:
                attrs['subjectexpr'] = base_subjectexpr
                inherited['subjectexpr'] = True

        errmessage = attrs.get('errmessage')
        if not errmessage:
            errmessage = constr_base.get_errmessage(schema)
            inherited['errmessage'] = True

        attrs['errmessage'] = errmessage

        if subject is not orig_subject:
            # subject has been redefined
            assert isinstance(subject, qlast.Base)
            qlutils.inline_anchors(
                expr_ql, anchors={qlast.Subject().name: subject})
            subject = orig_subject

        if args:
            args_map = None
            args_ql: List[qlast.Base] = [
                qlast.Path(steps=[qlast.Subject()]),
            ]

            args_ql.extend(
                qlparser.parse(arg.text, module_aliases) for arg in args
            )

            args_map = qlutils.index_parameters(
                args_ql,
                parameters=constr_base.get_params(schema),
                schema=schema)

            qlutils.inline_parameters(expr_ql, args_map)

        attrs['args'] = args

        if expr == '__subject__':
            expr_context = sourcectx
        else:
            expr_context = None

        assert subject is not None
        final_expr = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr_ql, schema, module_aliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=module_aliases,
                anchors={qlast.Subject().name: subject},
            ),
        )

        bool_t: s_scalars.ScalarType = schema.get('std::bool')
        assert isinstance(final_expr.irast, ir_ast.Statement)

        expr_type = final_expr.irast.stype
        if not expr_type.issubclass(schema, bool_t):
            raise errors.InvalidConstraintDefinitionError(
                f'{name} constraint expression expected '
                f'to return a bool value, got '
                f'{expr_type.get_verbosename(schema)}',
                context=expr_context
            )

        attrs['return_type'] = constr_base.get_return_type(schema)
        attrs['return_typemod'] = constr_base.get_return_typemod(schema)
        attrs['finalexpr'] = final_expr
        attrs['params'] = constr_base.get_params(schema)
        attrs['is_abstract'] = False

        return constr_base, attrs, inherited
Exemplo n.º 26
0
    def compile_function(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        body: expr.Expression,
    ) -> expr.Expression:
        from edb.ir import ast as irast

        params = self._get_params(schema, context)
        session_only = self._get_attribute_value(
            schema, context, 'session_only')

        language = self._get_attribute_value(schema, context, 'language')
        assert language is qlast.Language.EdgeQL

        has_inlined_defaults = bool(params.find_named_only(schema))

        param_anchors = get_params_symtable(
            params,
            schema,
            inlined_defaults=has_inlined_defaults,
        )

        compiled = type(body).compiled(
            body,
            schema,
            options=qlcompiler.CompilerOptions(
                anchors=param_anchors,
                func_params=params,
                # the body of a session_only function can contain calls to
                # other session_only functions
                session_mode=session_only,
            ),
        )

        ir = compiled.irast
        assert isinstance(ir, irast.Statement)
        schema = ir.schema

        return_type = self._get_attribute_value(schema, context, 'return_type')
        if (not ir.stype.issubclass(schema, return_type)
                and not ir.stype.implicitly_castable_to(return_type, schema)):
            raise errors.InvalidFunctionDefinitionError(
                f'return type mismatch in function declared to return '
                f'{return_type.get_verbosename(schema)}',
                details=f'Actual return type is '
                        f'{ir.stype.get_verbosename(schema)}',
                context=body.qlast.context,
            )

        return_typemod = self._get_attribute_value(
            schema, context, 'return_typemod')
        if (return_typemod is not ft.TypeModifier.SET_OF
                and ir.cardinality.is_multi()):
            raise errors.InvalidFunctionDefinitionError(
                f'return cardinality mismatch in function declared to return '
                f'a singleton',
                details=(
                    f'Function may return a set with more than one element.'
                ),
                context=body.qlast.context,
            )

        return compiled
Exemplo n.º 27
0
    def _parse_computable(
        self,
        expr: qlast.Base,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]:
        from edb.ir import ast as irast
        from edb.ir import typeutils as irtyputils
        from edb.schema import objtypes as s_objtypes

        # "source" attribute is set automatically as a refdict back-attr
        parent_ctx = self.get_referrer_context(context)
        assert parent_ctx is not None
        source_name = parent_ctx.op.classname

        source = schema.get(source_name, type=s_objtypes.ObjectType)
        expression = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr, schema, context.modaliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=context.modaliases,
                anchors={qlast.Source().name: source},
                path_prefix_anchor=qlast.Source().name,
                singletons=frozenset([source]),
            ),
        )

        assert isinstance(expression.irast, irast.Statement)
        base = None
        target = expression.irast.stype

        result_expr = expression.irast.expr.expr

        if (isinstance(result_expr, irast.SelectStmt)
                and result_expr.result.rptr is not None):
            expr_rptr = result_expr.result.rptr
            while isinstance(expr_rptr, irast.TypeIntersectionPointer):
                expr_rptr = expr_rptr.source.rptr

            is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound)

            if is_ptr_alias:
                schema, base = irtyputils.ptrcls_from_ptrref(expr_rptr.ptrref,
                                                             schema=schema)

        self.set_attribute_value('expr', expression)
        required, card = expression.irast.cardinality.to_schema_value()
        spec_required = self.get_attribute_value('required')
        spec_card = self.get_attribute_value('cardinality')

        # If cardinality was unspecified and the computable is not
        # required, use the inferred cardinality.
        if spec_card is None and not spec_required:
            self.set_attribute_value('required', required)
            self.set_attribute_value('cardinality', card)
        else:
            # Otherwise honor the spec, so no cardinality change, but check
            # that it's valid.

            if spec_card is None:
                # A computable link is marked explicitly as
                # "required", so we assume that omitted cardinality is
                # "single". Basically, to infer the cardinality both
                # cardinality-related qualifiers need to be omitted.
                spec_card = qltypes.SchemaCardinality.ONE

            if spec_required and not required:
                ptr_name = sn.shortname_from_fullname(
                    self.get_attribute_value('name')).name
                srcctx = self.get_attribute_source_context('target')
                raise errors.QueryError(
                    f'possibly an empty set returned by an '
                    f'expression for a computable '
                    f'{ptr_name!r} '
                    f"declared as 'required'",
                    context=srcctx)
            if (spec_card is qltypes.SchemaCardinality.ONE
                    and card != spec_card):
                ptr_name = sn.shortname_from_fullname(
                    self.get_attribute_value('name')).name
                srcctx = self.get_attribute_source_context('target')
                raise errors.QueryError(
                    f'possibly more than one element returned by an '
                    f'expression for a computable '
                    f'{ptr_name!r} '
                    f"declared as 'single'",
                    context=srcctx)

        self.set_attribute_value('computable', True)

        return schema, target, base
Exemplo n.º 28
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)))
Exemplo n.º 29
0
    def schema_constraint_to_backend_constraint(cls, subject, constraint,
                                                schema):
        assert constraint.get_subject(schema) is not None

        ir = qlcompiler.compile_ast_to_ir(
            constraint.get_finalexpr(schema).qlast,
            schema,
            options=qlcompiler.CompilerOptions(
                anchors={qlast.Subject().name: subject}, ),
        )

        terminal_refs = ir_utils.get_longest_paths(ir.expr.expr.result)
        ref_tables = cls._get_ref_storage_info(ir.schema, terminal_refs)

        if len(ref_tables) > 1:
            raise ValueError(
                'backend: multi-table constraints are not currently supported')
        elif ref_tables:
            subject_db_name, refs = next(iter(ref_tables.items()))
            link_bias = refs[0][3].table_type == 'link'
        else:
            subject_db_name = common.get_backend_name(schema,
                                                      subject,
                                                      catenate=False)
            link_bias = False

        exclusive_expr_refs = cls._get_exclusive_refs(ir)

        pg_constr_data = {
            'subject_db_name': subject_db_name,
            'expressions': []
        }

        constraint_origin = cls._get_constraint_origin(schema, constraint)
        if constraint_origin != constraint:
            origin_subject = constraint_origin.get_subject(schema)
            origin_ir = qlcompiler.compile_ast_to_ir(
                constraint_origin.get_finalexpr(schema).qlast,
                schema,
                options=qlcompiler.CompilerOptions(
                    anchors={qlast.Subject().name: origin_subject}, ),
            )

            origin_terminal_refs = ir_utils.get_longest_paths(
                origin_ir.expr.expr.result)
            origin_ref_tables = cls._get_ref_storage_info(
                origin_ir.schema, origin_terminal_refs)

            if origin_ref_tables:
                origin_subject_db_name, _ = (next(
                    iter(origin_ref_tables.items())))
            else:
                origin_subject_db_name = common.get_backend_name(
                    schema,
                    origin_subject,
                    catenate=False,
                )

            pg_constr_data['origin_subject_db_name'] = origin_subject_db_name
        else:
            pg_constr_data['origin_subject_db_name'] = subject_db_name

        exprs = pg_constr_data['expressions']

        if exclusive_expr_refs:
            for ref in exclusive_expr_refs:
                exprdata = cls._edgeql_ref_to_pg_constr(
                    subject, ref, schema, link_bias)
                exprs.append(exprdata)

            pg_constr_data['scope'] = 'relation'
            pg_constr_data['type'] = 'unique'
        else:
            exprdata = cls._edgeql_ref_to_pg_constr(subject, ir, schema,
                                                    link_bias)
            exprs.append(exprdata)

            pg_constr_data['scope'] = 'row'
            pg_constr_data['type'] = 'check'

        if isinstance(constraint.get_subject(schema), s_scalars.ScalarType):
            constraint = SchemaDomainConstraint(subject=subject,
                                                constraint=constraint,
                                                pg_constr_data=pg_constr_data,
                                                schema=schema)
        else:
            constraint = SchemaTableConstraint(subject=subject,
                                               constraint=constraint,
                                               pg_constr_data=pg_constr_data,
                                               schema=schema)
        return constraint