Beispiel #1
0
    def normalize_constraint_expr(cls,
                                  schema,
                                  module_aliases,
                                  expr,
                                  *,
                                  subject=None,
                                  constraint_name,
                                  expr_context=None,
                                  enforce_boolean=False):

        if subject is None:
            subject = cls._dummy_subject(schema)

        edgeql_tree, ir_result = cls._normalize_constraint_expr(
            schema, module_aliases, expr, subject)

        if enforce_boolean:
            bool_t = schema.get('std::bool')
            expr_type = ir_result.stype
            if not expr_type.issubclass(schema, bool_t):
                raise errors.InvalidConstraintDefinitionError(
                    f'{constraint_name} constraint expression expected '
                    f'to return a bool value, got '
                    f'{expr_type.get_name(schema).name!r}',
                    context=expr_context)

        expr = edgeql.generate_source(edgeql_tree, pretty=False)
        return expr
Beispiel #2
0
def statements_from_delta(
    schema_a: Optional[s_schema.Schema],
    schema_b: s_schema.Schema,
    delta: sd.DeltaRoot,
    *,
    sdlmode: bool = False,
    descriptive_mode: bool = False,
    limit_ref_classes: Iterable[so.ObjectMeta] = tuple(),
) -> Tuple[Tuple[str, sd.Command], ...]:

    stmts = ddlast_from_delta(
        schema_a,
        schema_b,
        delta,
        sdlmode=sdlmode,
        descriptive_mode=descriptive_mode,
    )

    ql_classes_src = {scls.get_ql_class() for scls in limit_ref_classes}

    ql_classes = {q for q in ql_classes_src if q is not None}

    text = []
    for stmt_ast, cmd in stmts.items():
        stmt_text = edgeql.generate_source(
            stmt_ast,
            sdlmode=sdlmode,
            descmode=descriptive_mode,
            limit_ref_classes=ql_classes,
        )
        text.append((stmt_text + ';', cmd))

    return tuple(text)
Beispiel #3
0
def _text_from_delta(
        schema: s_schema.Schema,
        delta: sd.DeltaRoot,
        *,
        sdlmode: bool,
        descriptive_mode: bool = False,
        limit_ref_classes: Iterable[so.ObjectMeta] = tuple(),
) -> str:

    context = sd.CommandContext(
        descriptive_mode=descriptive_mode,
        declarative=sdlmode,
    )
    text = []
    for command in delta.get_subcommands():
        with context(sd.DeltaRootContext(schema=schema, op=delta)):
            delta_ast = command.get_ast(schema, context)
            if delta_ast:
                ql_classes_src = {
                    scls.get_ql_class()
                    for scls in limit_ref_classes
                }
                ql_classes = {q for q in ql_classes_src if q is not None}

                stmt_text = edgeql.generate_source(
                    delta_ast,
                    sdlmode=sdlmode,
                    descmode=descriptive_mode,
                    limit_ref_classes=ql_classes,
                )
                text.append(stmt_text + ';')

    return '\n'.join(text)
    def _encode_default(self, schema, context, node, op):
        if op.new_value:
            expr = op.new_value
            if not isinstance(expr, s_expr.Expression):
                expr_t = qlast.SelectQuery(
                    result=qlast.BaseConstant.from_python(expr))
                expr = edgeql.generate_source(expr_t, pretty=False)

                op.new_value = s_expr.Expression(expr)
            super()._apply_field_ast(schema, context, node, op)
Beispiel #5
0
    def _constraint_args_from_ast(cls, schema, astnode):
        args = []

        if astnode.args:
            for arg in astnode.args:
                arg_expr = s_expr.Expression(
                    text=edgeql.generate_source(arg.arg, pretty=False))
                args.append(arg_expr)

        return args
Beispiel #6
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        cmd.set_attribute_value(
            'expr',
            s_expr.Expression(
                text=edgeql.generate_source(astnode.expr, pretty=False)
            ),
        )

        return cmd
Beispiel #7
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)

        if isinstance(astnode, qlast.CreateConcreteConstraint):
            args = cls._constraint_args_from_ast(schema, astnode)
            if args:
                cmd.add(sd.AlterObjectProperty(property='args',
                                               new_value=args))

        elif isinstance(astnode, qlast.CreateConstraint):
            params = cls._get_param_desc_from_ast(schema, context.modaliases,
                                                  astnode)

            for param in params:
                if param.get_kind(schema) is ft.ParameterKind.NAMED_ONLY:
                    raise errors.InvalidConstraintDefinitionError(
                        'named only parameters are not allowed '
                        'in this context',
                        context=astnode.context)

                if param.get_default(schema) is not None:
                    raise errors.InvalidConstraintDefinitionError(
                        'constraints do not support parameters '
                        'with defaults',
                        context=astnode.context)

        if cmd.get_attribute_value('return_type') is None:
            cmd.add(
                sd.AlterObjectProperty(property='return_type',
                                       new_value=utils.reduce_to_typeref(
                                           schema, schema.get('std::bool'))))

        if cmd.get_attribute_value('return_typemod') is None:
            cmd.add(
                sd.AlterObjectProperty(
                    property='return_typemod',
                    new_value=ft.TypeModifier.SINGLETON,
                ))

        # 'subjectexpr' can be present in either astnode type
        if astnode.subjectexpr:
            subjectexpr = s_expr.Expression(
                text=edgeql.generate_source(astnode.subjectexpr, pretty=False))

            cmd.add(
                sd.AlterObjectProperty(property='subjectexpr',
                                       new_value=subjectexpr))

        cls._validate_subcommands(astnode)

        return cmd
Beispiel #8
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
Beispiel #9
0
def ddl_text_from_delta_command(schema, delta):
    """Return DDL text for a delta command tree."""
    if isinstance(delta, sd.DeltaRoot):
        commands = delta
    else:
        commands = [delta]

    text = []
    for command in commands:
        delta_ast = ddl_from_delta(schema, command)
        if delta_ast:
            stmt_text = edgeql.generate_source(delta_ast)
            text.append(stmt_text + ';')

    return '\n'.join(text)
Beispiel #10
0
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        from edb.edgeql import compiler as qlcompiler

        propname = astnode.name.name

        parent_ctx = context.get(CommandContextToken)
        parent_op = parent_ctx.op
        field = parent_op.get_schema_metaclass().get_field(propname)
        if field is None:
            raise errors.SchemaDefinitionError(
                f'{propname!r} is not a valid field', context=astnode.context)

        if not (isinstance(astnode, qlast.SetInternalField)
                or field.allow_ddl_set or context.stdmode or context.testmode):
            raise errors.SchemaDefinitionError(
                f'{propname!r} is not a valid field', context=astnode.context)

        if field.type is s_expr.Expression:
            new_value = s_expr.Expression(
                text=edgeql.generate_source(astnode.value, pretty=False))
        else:
            if isinstance(astnode.value, qlast.Tuple):
                new_value = tuple(
                    qlcompiler.evaluate_ast_to_python_val(el.value,
                                                          schema=schema)
                    for el in astnode.value.elements)

            elif isinstance(astnode.value, qlast.ObjectRef):

                new_value = utils.ast_objref_to_objref(
                    astnode.value,
                    modaliases=context.modaliases,
                    schema=schema)

            elif (isinstance(astnode.value, qlast.Set)
                  and not astnode.value.elements):
                # empty set
                new_value = None

            else:
                new_value = qlcompiler.evaluate_ast_to_python_val(
                    astnode.value, schema=schema)

        return cls(property=propname, new_value=new_value)
Beispiel #11
0
def ddl_text_from_delta(schema, delta):
    """Return DDL text for a delta object."""

    root = sd.DeltaRoot(canonical=True)
    root.update(delta.get_commands(schema))

    context = sd.CommandContext()
    schema, _ = root.apply(schema, context)

    context = sd.CommandContext()
    text = []
    for command in root.get_subcommands():
        with context(sd.DeltaRootContext(schema=schema, op=root)):
            delta_ast = ddl_from_delta(schema, context, command)
            if delta_ast:
                stmt_text = edgeql.generate_source(delta_ast)
                text.append(stmt_text + ';')

    return '\n'.join(text)
Beispiel #12
0
def ddl_text_from_delta(schema: s_schema.Schema, delta: sd.DeltaRoot) -> str:
    """Return DDL text corresponding to a delta plan.

    Args:
        schema:
            The schema to which the *delta* has **already** been
            applied.
        delta:
            The delta plan.

    Returns:
        DDL text corresponding to *delta*.
    """
    context = sd.CommandContext()
    text = []
    for command in delta.get_subcommands():
        with context(sd.DeltaRootContext(schema=schema, op=delta)):
            delta_ast = command.get_ast(schema, context)
            if delta_ast:
                stmt_text = edgeql.generate_source(delta_ast)
                text.append(stmt_text + ';')

    return '\n'.join(text)
Beispiel #13
0
    def _classname_quals_from_ast(cls, schema, astnode, base_name,
                                  referrer_name, context):
        subject = schema.get(referrer_name, None)
        if subject is None:
            return ()

        props = {}
        args = cls._constraint_args_from_ast(schema, astnode)
        if args:
            props['args'] = args
        if astnode.subjectexpr:
            props['subjectexpr'] = s_expr.Expression(
                text=edgeql.generate_source(astnode.subject, pretty=False))

        _, attrs = Constraint.get_concrete_constraint_attrs(
            schema,
            subject,
            name=base_name,
            sourcectx=astnode.context,
            modaliases=context.modaliases,
            **props)

        return (Constraint._name_qual_from_expr(schema,
                                                attrs['finalexpr'].text), )
Beispiel #14
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
Beispiel #15
0
def statements_from_delta(
    schema_a: Optional[s_schema.Schema],
    schema_b: s_schema.Schema,
    delta: sd.DeltaRoot,
    *,
    sdlmode: bool = False,
    descriptive_mode: bool = False,
    # Used for backwards compatibility with older migration text.
    uppercase: bool = False,
    limit_ref_classes: Iterable[so.ObjectMeta] = tuple(),
) -> Tuple[Tuple[str, qlast.DDLOperation, sd.Command], ...]:

    stmts = ddlast_from_delta(
        schema_a,
        schema_b,
        delta,
        sdlmode=sdlmode,
        descriptive_mode=descriptive_mode,
    )

    ql_classes_src = {
        scls.get_ql_class() for scls in limit_ref_classes
    }

    ql_classes = {q for q in ql_classes_src if q is not None}

    # If we're generating SDL and it includes modules, try to nest the
    # module contents in the actual modules.
    processed: List[Tuple[qlast.DDLOperation, sd.Command]] = []
    unqualified: List[Tuple[qlast.DDLOperation, sd.Command]] = []
    modules = dict()
    for stmt_ast, cmd in stmts.items():
        if sdlmode:
            if isinstance(stmt_ast, qlast.CreateModule):
                # Record the module stubs.
                modules[stmt_ast.name.name] = stmt_ast
                stmt_ast.commands = []
                processed.append((stmt_ast, cmd))

            elif (
                modules
                and not isinstance(stmt_ast, qlast.UnqualifiedObjectCommand)
            ):
                # This SDL included creation of modules, so we will try to
                # nest the declarations in them.
                assert isinstance(stmt_ast, qlast.CreateObject)
                assert stmt_ast.name.module is not None
                module = modules[stmt_ast.name.module]
                module.commands.append(stmt_ast)
                # Strip the module from the object name, since we nest
                # them in a module already.
                stmt_ast.name.module = None

            elif isinstance(stmt_ast, qlast.UnqualifiedObjectCommand):
                unqualified.append((stmt_ast, cmd))

            else:
                processed.append((stmt_ast, cmd))

        else:
            processed.append((stmt_ast, cmd))

    text = []
    for stmt_ast, cmd in itertools.chain(unqualified, processed):
        stmt_text = edgeql.generate_source(
            stmt_ast,
            sdlmode=sdlmode,
            descmode=descriptive_mode,
            limit_ref_classes=ql_classes,
            uppercase=uppercase,
        )
        text.append((stmt_text + ';', stmt_ast, cmd))

    return tuple(text)
Beispiel #16
0
    def run_ddl(cls, schema, ddl, default_module=defines.DEFAULT_MODULE_ALIAS):
        statements = edgeql.parse_block(ddl)

        current_schema = schema
        target_schema = None
        migration_schema = None
        migration_target = None
        migration_script = []

        for stmt in statements:
            if isinstance(stmt, qlast.StartMigration):
                # START MIGRATION
                if target_schema is None:
                    target_schema = _load_std_schema()

                migration_target = s_ddl.apply_sdl(
                    stmt.target,
                    base_schema=target_schema,
                    current_schema=current_schema,
                    testmode=True,
                )

                migration_schema = current_schema

                ddl_plan = None

            elif isinstance(stmt, qlast.PopulateMigration):
                # POPULATE MIGRATION
                if migration_target is None:
                    raise errors.QueryError(
                        'unexpected POPULATE MIGRATION:'
                        ' not currently in a migration block',
                        context=stmt.context,
                    )

                migration_diff = s_ddl.delta_schemas(
                    migration_schema,
                    migration_target,
                )

                if debug.flags.delta_plan:
                    debug.header('Populate Migration Diff')
                    debug.dump(migration_diff, schema=schema)

                new_ddl = s_ddl.ddlast_from_delta(
                    migration_schema,
                    migration_target,
                    migration_diff,
                )

                migration_script.extend(new_ddl)

                if debug.flags.delta_plan:
                    debug.header('Populate Migration DDL AST')
                    text = []
                    for cmd in new_ddl:
                        debug.dump(cmd)
                        text.append(edgeql.generate_source(cmd, pretty=True))
                    debug.header('Populate Migration DDL Text')
                    debug.dump_code(';\n'.join(text) + ';')

            elif isinstance(stmt, qlast.CommitMigration):
                if migration_target is None:
                    raise errors.QueryError(
                        'unexpected COMMIT MIGRATION:'
                        ' not currently in a migration block',
                        context=stmt.context,
                    )

                last_migration = current_schema.get_last_migration()
                if last_migration:
                    last_migration_ref = s_utils.name_to_ast_ref(
                        last_migration.get_name(current_schema), )
                else:
                    last_migration_ref = None

                create_migration = qlast.CreateMigration(
                    body=qlast.MigrationBody(commands=tuple(migration_script)),
                    parent=last_migration_ref,
                )

                ddl_plan = s_ddl.delta_from_ddl(
                    create_migration,
                    schema=migration_schema,
                    modaliases={None: default_module},
                    testmode=True,
                )

                if debug.flags.delta_plan:
                    debug.header('Delta Plan')
                    debug.dump(ddl_plan, schema=schema)

                migration_schema = None
                migration_target = None
                migration_script = []

            elif isinstance(stmt, qlast.DDL):
                if migration_target is not None:
                    migration_script.append(stmt)
                    ddl_plan = None
                else:
                    ddl_plan = s_ddl.delta_from_ddl(
                        stmt,
                        schema=current_schema,
                        modaliases={None: default_module},
                        testmode=True,
                    )

                    if debug.flags.delta_plan:
                        debug.header('Delta Plan')
                        debug.dump(ddl_plan, schema=schema)
            else:
                raise ValueError(
                    f'unexpected {stmt!r} in compiler setup script')

            if ddl_plan is not None:
                context = sd.CommandContext()
                context.testmode = True
                current_schema = ddl_plan.apply(current_schema, context)

        return current_schema
Beispiel #17
0
    def get_concrete_constraint_attrs(cls,
                                      schema,
                                      subject,
                                      *,
                                      name,
                                      subjectexpr=None,
                                      sourcectx=None,
                                      args=[],
                                      modaliases=None,
                                      **kwargs):
        from edb.edgeql import utils as edgeql_utils
        from edb.edgeql import parser as edgeql_parser

        constr_base = schema.get(name, module_aliases=modaliases)
        module_aliases = {}

        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, _ = cls._normalize_constraint_expr(
                schema, {}, subjectexpr.text, subject)

        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 = edgeql_parser.parse(expr.text, module_aliases)

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

        attrs = dict(kwargs)

        args_map = None
        if args:
            args_ql = [
                edgeql_parser.parse(arg.text, module_aliases) for arg in args
            ]

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

            edgeql_utils.inline_parameters(expr_ql, args_map)

            args_map = {
                name: edgeql.generate_source(val, pretty=False)
                for name, val in args_map.items()
            }

            errmessage = attrs.get('errmessage')
            if not errmessage:
                errmessage = constr_base.get_errmessage(schema)

            attrs['errmessage'] = errmessage.format(
                __subject__='{__subject__}', **args_map)

        attrs['args'] = args

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

        if subject is not orig_subject:
            # subject has been redefined
            subject_anchor = qlast.SubExpr(
                expr=subject, anchors={qlast.Subject: orig_subject})
        else:
            subject_anchor = subject

        expr_text = cls.normalize_constraint_expr(schema,
                                                  module_aliases,
                                                  expr_ql,
                                                  subject=subject_anchor,
                                                  constraint_name=name,
                                                  enforce_boolean=True,
                                                  expr_context=expr_context)

        attrs['finalexpr'] = s_expr.Expression(text=expr_text)

        return constr_base, attrs