Ejemplo n.º 1
0
class CreateDatabase(DatabaseCommand, sd.CreateExternalObject[Database]):

    astnode = qlast.CreateDatabase
    template = struct.Field(str, default=None)

    @classmethod
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astnode: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> CreateDatabase:
        cmd = super()._cmd_tree_from_ast(schema, astnode, context)
        assert isinstance(cmd, CreateDatabase)

        assert isinstance(astnode, qlast.CreateDatabase)
        if astnode.template is not None:
            if not context.testmode:
                raise errors.EdgeQLSyntaxError(
                    f'unexpected {astnode.template.name!r}',
                    context=astnode.template.context,
                )
            cmd.template = astnode.template.name

        return cmd
Ejemplo n.º 2
0
class Expression(struct.Struct):
    text = struct.Field(str, frozen=True)
    qlast = struct.Field(ql_ast.Base, default=None, frozen=True)
    irast = struct.Field(object, default=None, frozen=True)
    refs = struct.Field(so.ObjectSet, coerce=True, default=None, frozen=True)

    @classmethod
    def compare_values(cls, ours, theirs, *, our_schema, their_schema, context,
                       compcoef):
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef
        elif ours.text == theirs.text:
            return 1.0
        else:
            return compcoef
Ejemplo n.º 3
0
class UnqualifiedObjectCommand(ObjectCommand):

    classname = struct.Field(str)

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        return astnode.name.name

    def get_object(self, schema, *, name=None):
        metaclass = self.get_schema_metaclass()
        if name is None:
            name = self.classname
        return schema.get_global(metaclass, name)
Ejemplo n.º 4
0
class RebaseReferencedInheritingObject(
        ReferencedInheritingObjectCommand[ReferencedInheritingObjectT],
        inheriting.RebaseInheritingObject[ReferencedInheritingObjectT],
):

    implicit = struct.Field(bool, default=False)

    def apply(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> s_schema.Schema:

        if not context.canonical and self.implicit:
            mcls = self.get_schema_metaclass()
            refctx = self.get_referrer_context_or_die(context)
            referrer = refctx.scls
            assert isinstance(referrer, so.InheritingObject)
            refdict = type(referrer).get_refdict_for_class(mcls)

            implicit_bases = self._get_implicit_ref_bases(
                schema,
                context,
                referrer=referrer,
                referrer_field=refdict.attr,
                fq_name=self.classname,
            )

            scls = self.get_object(schema, context)
            removed_bases, added_bases = self.get_ref_implicit_base_delta(
                schema,
                context,
                scls,
                implicit_bases=implicit_bases,
            )

            self.added_bases = added_bases
            self.removed_bases = removed_bases

        return super().apply(schema, context)

    def _get_bases_for_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        bases: Tuple[so.ObjectShell, ...],
    ) -> Tuple[so.ObjectShell, ...]:
        bases = super()._get_bases_for_ast(schema, context, bases)
        implicit_bases = set(self.get_implicit_bases(schema, context, bases))
        return tuple(b for b in bases if b.name not in implicit_bases)
Ejemplo n.º 5
0
class Expression(struct.MixedStruct):
    text = struct.Field(str, frozen=True)
    origtext = struct.Field(str, default=None, frozen=True)
    irast = struct.Field(object, default=None, frozen=True)
    refs = struct.Field(so.ObjectSet, coerce=True, default=None, frozen=True)

    def __init__(self, *args, _qlast=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._qlast = _qlast

    @property
    def qlast(self):
        return self._qlast

    @classmethod
    def compare_values(cls, ours, theirs, *, our_schema, their_schema, context,
                       compcoef):
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef
        elif ours.text == theirs.text:
            return 1.0
        else:
            return compcoef

    @classmethod
    def from_ast(cls, qltree, schema, modaliases):
        norm_tree = imprint_expr_context(qltree, modaliases)
        norm_text = qlcodegen.generate_source(norm_tree, pretty=False)

        return cls(
            text=norm_text,
            origtext=qlcodegen.generate_source(qltree),
            _qlast=norm_tree,
        )
Ejemplo n.º 6
0
class Command(struct.MixedStruct, metaclass=CommandMeta):
    """Abstract base class for all delta commands."""

    source_context = struct.Field(object, default=None)

    _context_class = None

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ops = ordered.OrderedSet()
        self.before_ops = ordered.OrderedSet()

    def copy(self):
        result = super().copy()
        result.ops = ordered.OrderedSet(op.copy() for op in self.ops)
        result.before_ops = ordered.OrderedSet(op.copy()
                                               for op in self.before_ops)
        return result

    @classmethod
    def adapt(cls, obj):
        result = obj.copy_with_class(cls)
        for op in obj.get_subcommands():
            result.ops.add(type(cls).adapt(op))
        return result

    def _resolve_type_ref(self, ref, schema):
        return utils.resolve_typeref(ref, schema)

    def _resolve_attr_value(self, value, fname, field, schema):
        ftype = field.type

        if isinstance(ftype, so.ObjectMeta):
            value = self._resolve_type_ref(value, schema)

        elif issubclass(ftype, typed.AbstractTypedMapping):
            if issubclass(ftype.valuetype, so.Object):
                vals = {}

                for k, val in value.items():
                    vals[k] = self._resolve_type_ref(val, schema)

                value = ftype(vals)

        elif issubclass(ftype,
                        (typed.AbstractTypedSequence, typed.AbstractTypedSet)):
            if issubclass(ftype.type, so.Object):
                vals = []

                for val in value:
                    vals.append(self._resolve_type_ref(val, schema))

                value = ftype(vals)

        else:
            value = field.coerce_value(schema, value)

        return value

    def get_struct_properties(self, schema):
        result = {}
        metaclass = self.get_schema_metaclass()

        for op in self.get_subcommands(type=AlterObjectProperty):
            field = metaclass.get_field(op.property)
            if field is None:
                raise errors.SchemaDefinitionError(
                    f'got AlterObjectProperty command for '
                    f'invalid field: {metaclass.__name__}.{op.property}')

            result[op.property] = self._resolve_attr_value(
                op.new_value, op.property, field, schema)

        return result

    def has_attribute_value(self, attr_name):
        for op in self.get_subcommands(type=AlterObjectProperty):
            if op.property == attr_name:
                return True
        return False

    def get_attribute_value(self, attr_name):
        for op in self.get_subcommands(type=AlterObjectProperty):
            if op.property == attr_name:
                return op.new_value
        else:
            return None

    def set_attribute_value(self, attr_name, value):
        for op in self.get_subcommands(type=AlterObjectProperty):
            if op.property == attr_name:
                op.new_value = value
                break
        else:
            self.add(AlterObjectProperty(property=attr_name, new_value=value))

    def discard_attribute(self, attr_name):
        for op in self.get_subcommands(type=AlterObjectProperty):
            if op.property == attr_name:
                self.discard(op)
                return

    def __iter__(self):
        for op in self.ops:
            yield from op.before_ops
            yield op

    def get_subcommands(self, *, type=None):
        if type is not None:
            return filter(lambda i: isinstance(i, type), self)
        else:
            return list(self)

    def has_subcommands(self):
        return bool(self.ops)

    def after(self, command):
        if isinstance(command, CommandGroup):
            for op in command:
                self.before_ops.add(command)
        else:
            self.before_ops.add(command)

    def prepend(self, command):
        if isinstance(command, CommandGroup):
            for op in reversed(command):
                self.ops.add(op, last=False)
        else:
            self.ops.add(command, last=False)

    def add(self, command):
        if isinstance(command, CommandGroup):
            self.update(command)
        else:
            self.ops.add(command)

    def update(self, commands):
        for command in commands:
            self.add(command)

    def discard(self, command):
        self.ops.discard(command)

    def sort_subcommands_by_type(self):
        def _key(c):
            if isinstance(c, CreateObject):
                return 0
            else:
                return 2

        self.ops = ordered.OrderedSet(sorted(self.ops, key=_key))

    def apply(self, schema, context):
        return schema, None

    def get_ast(self, schema, context):
        if context is None:
            context = CommandContext()

        with self.new_context(schema, context):
            return self._get_ast(schema, context)

    @classmethod
    def command_for_ast_node(cls, astnode, schema, context):
        cmdcls = type(cls)._astnode_map.get(type(astnode))
        if hasattr(cmdcls, '_command_for_ast_node'):
            # Delegate the choice of command class to the specific command.
            cmdcls = cmdcls._command_for_ast_node(astnode, schema, context)

        return cmdcls

    @classmethod
    def from_ast(cls, schema, astnode, *, context=None):
        if context is None:
            context = CommandContext()

        cmdcls = cls.command_for_ast_node(astnode,
                                          schema=schema,
                                          context=context)

        if cmdcls is None:
            msg = 'cannot find command for ast node {!r}'.format(astnode)
            raise TypeError(msg)

        return cmdcls._cmd_tree_from_ast(schema, astnode, context)

    @classmethod
    def _cmd_tree_from_ast(cls, schema, astnode, context):
        cmd = cls._cmd_from_ast(schema, astnode, context)
        cmd.source_context = astnode.context

        if getattr(astnode, 'commands', None):
            context_class = cls.get_context_class()

            if context_class is not None:
                with context(context_class(schema, cmd, scls=None)):
                    for subastnode in astnode.commands:
                        subcmd = Command.from_ast(schema,
                                                  subastnode,
                                                  context=context)
                        if subcmd is not None:
                            cmd.add(subcmd)
            else:
                for subastnode in astnode.commands:
                    subcmd = Command.from_ast(schema,
                                              subastnode,
                                              context=context)
                    if subcmd is not None:
                        cmd.add(subcmd)

        return cmd

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        return cls()

    @classmethod
    def as_markup(cls, self, *, ctx):
        node = markup.elements.lang.TreeNode(name=str(self))

        for dd in self:
            if isinstance(dd, AlterObjectProperty):
                diff = markup.elements.doc.ValueDiff(before=repr(dd.old_value),
                                                     after=repr(dd.new_value))

                node.add_child(label=dd.property, node=diff)
            else:
                node.add_child(node=markup.serialize(dd, ctx=ctx))

        return node

    @classmethod
    def get_context_class(cls):
        return cls._context_class

    def new_context(self, schema, context, scls=_void):
        if context is None:
            context = CommandContext()

        if scls is _void:
            scls = getattr(self, 'scls', None)

        context_class = self.get_context_class()
        return context(context_class(schema, self, scls))

    def __str__(self):
        return struct.MixedStruct.__str__(self)

    def __repr__(self):
        flds = struct.MixedStruct.__repr__(self)
        return '<{}.{}{}>'.format(self.__class__.__module__,
                                  self.__class__.__name__,
                                  (' ' + flds) if flds else '')
Ejemplo n.º 7
0
class AlterObjectProperty(Command):
    astnode = (qlast.SetField, qlast.SetInternalField)

    property = struct.Field(str)
    old_value = struct.Field(object, None)
    new_value = struct.Field(object, None)
    source = struct.Field(str, None)

    @classmethod
    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.from_ast(
                astnode.value,
                schema,
                context.modaliases,
            )
        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)

    def _get_ast(self, schema, context):
        value = self.new_value

        new_value_empty = \
            (value is None or
                (isinstance(value, collections.abc.Container) and not value))

        old_value_empty = \
            (self.old_value is None or
                (isinstance(self.old_value, collections.abc.Container) and
                    not self.old_value))

        if new_value_empty and not old_value_empty:
            op = qlast.DropAnnotationValue(
                name=qlast.ObjectRef(module='', name=self.property))
            return op

        if new_value_empty and old_value_empty:
            return

        if isinstance(value, s_expr.Expression):
            value = edgeql.parse(value.text)
        elif utils.is_nontrivial_container(value):
            value = qlast.Tuple(
                elements=[qlast.BaseConstant.from_python(el) for el in value])
        else:
            value = qlast.BaseConstant.from_python(value)

        op = qlast.SetField(name=qlast.ObjectRef(module='',
                                                 name=self.property),
                            value=value)
        return op

    def __repr__(self):
        return '<%s.%s "%s":"%s"->"%s">' % (
            self.__class__.__module__, self.__class__.__name__, self.property,
            self.old_value, self.new_value)
Ejemplo n.º 8
0
class Code(BaseCode):
    tokens = struct.Field(TokenList, default=None, coerce=True)
Ejemplo n.º 9
0
class RebaseInheritingObject(
        AlterInheritingObjectFragment[so.InheritingObjectT], ):
    _delta_action = 'rebase'

    removed_bases = struct.Field(tuple)  # type: ignore
    added_bases = struct.Field(tuple)  # type: ignore

    def __repr__(self) -> str:
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__, self.classname)

    def apply(self, schema: s_schema.Schema,
              context: sd.CommandContext) -> s_schema.Schema:
        schema = super().apply(schema, context)
        scls = self.scls

        assert isinstance(scls, so.InheritingObject)

        if not context.canonical:
            bases = self._apply_base_delta(schema, context, scls)
            self.set_attribute_value(
                'bases',
                bases,
                orig_value=scls.get_bases(schema),
            )
            schema = scls.set_field_value(schema, 'bases', bases)

            schema = self._recompute_inheritance(schema, context)

            if context.enable_recursion:
                for descendant in scls.ordered_descendants(schema):
                    d_root_cmd, d_alter_cmd, ctx_stack = (
                        descendant.init_delta_branch(schema, context,
                                                     sd.AlterObject))
                    assert isinstance(d_alter_cmd, InheritingObjectCommand)
                    with ctx_stack():
                        schema = d_alter_cmd._recompute_inheritance(
                            schema, context)
                    self.add(d_root_cmd)

        assert isinstance(scls, so.InheritingObject)

        return schema

    def _apply_base_delta(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        scls: so.InheritingObjectT,
    ) -> so.ObjectList[so.InheritingObjectT]:
        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base: Optional[so.InheritingObjectT] = self.get_object(
                schema, context, name=default_base_name)
            if bases == [default_base]:
                bases = []
        else:
            default_base = None

        removed_bases = {b.name for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref.name]

            bases[idx:idx] = [
                self.get_object(schema, context, name=b.name)
                for b in new_bases if b.name not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases and default_base:
            bases = [default_base]

        return so.ObjectList[so.InheritingObjectT].create(schema, bases)

    def _get_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        *,
        parent_node: Optional[qlast.DDLOperation] = None,
    ) -> Optional[qlast.DDLOperation]:
        assert parent_node is not None

        dropped = self._get_bases_for_ast(schema, context, self.removed_bases)

        if dropped:
            parent_node.commands.append(
                qlast.AlterDropInherit(bases=[
                    utils.typeref_to_ast(schema, b) for b in dropped
                ], ))

        for bases, pos in self.added_bases:
            bases = self._get_bases_for_ast(schema, context, bases)
            if not bases:
                continue

            if isinstance(pos, tuple):
                pos_node = qlast.Position(
                    position=pos[0],
                    ref=utils.typeref_to_ast(schema, pos[1]),
                )
            else:
                pos_node = qlast.Position(position=pos)

            parent_node.commands.append(
                qlast.AlterAddInherit(
                    bases=[utils.typeref_to_ast(schema, b) for b in bases],
                    position=pos_node,
                ))

        return None

    def _get_bases_for_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        bases: Tuple[so.ObjectShell, ...],
    ) -> Tuple[so.ObjectShell, ...]:
        mcls = self.get_schema_metaclass()
        roots = set(mcls.get_root_classes())
        return tuple(b for b in bases if b.name not in roots)
Ejemplo n.º 10
0
class AlterInherit(sd.Command):
    astnode = qlast.AlterAddInherit, qlast.AlterDropInherit

    # We temporarily record information about inheritance alterations
    # here, before converting these into Rebases in AlterObject.  The
    # goal here is to encode the information in the subcommand stream,
    # so the positioning is maintained.
    added_bases = struct.Field(
        List[Tuple[List[so.ObjectShell],
                   Optional[Union[str, Tuple[str, so.ObjectShell]]], ]])
    dropped_bases = struct.Field(List[so.ObjectShell])

    @classmethod
    def _cmd_tree_from_ast(
        cls,
        schema: s_schema.Schema,
        astcmd: qlast.DDLOperation,
        context: sd.CommandContext,
    ) -> Any:
        added_bases = []
        dropped_bases: List[so.ObjectShell] = []

        parent_op = context.current().op
        assert isinstance(parent_op, sd.ObjectCommand)
        parent_mcls = parent_op.get_schema_metaclass()

        if isinstance(astcmd, qlast.AlterDropInherit):
            dropped_bases.extend(
                utils.ast_to_object_shell(
                    b,
                    metaclass=parent_mcls,
                    modaliases=context.modaliases,
                    schema=schema,
                ) for b in astcmd.bases)

        elif isinstance(astcmd, qlast.AlterAddInherit):
            bases = [
                utils.ast_to_object_shell(
                    b,
                    metaclass=parent_mcls,
                    modaliases=context.modaliases,
                    schema=schema,
                ) for b in astcmd.bases
            ]

            pos_node = astcmd.position
            pos: Optional[Union[str, Tuple[str, so.ObjectShell]]]
            if pos_node is not None:
                if pos_node.ref is not None:
                    ref = so.ObjectShell(
                        name=utils.ast_ref_to_name(pos_node.ref),
                        schemaclass=parent_mcls,
                    )
                    pos = (pos_node.position, ref)
                else:
                    pos = pos_node.position
            else:
                pos = None

            added_bases.append((bases, pos))

        # AlterInheritingObject will turn sequences of AlterInherit
        # into proper RebaseWhatever commands.
        return AlterInherit(added_bases=added_bases,
                            dropped_bases=dropped_bases)
Ejemplo n.º 11
0
class RebaseInheritingObject(sd.ObjectCommand):
    _delta_action = 'rebase'

    new_base = struct.Field(tuple, default=tuple())
    removed_bases = struct.Field(tuple)
    added_bases = struct.Field(tuple)

    def __repr__(self):
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__, self.classname)

    def apply(self, schema, context):
        metaclass = self.get_schema_metaclass()
        scls = self.get_object(schema, context)
        self.scls = scls

        schema, props = self._get_field_updates(schema, context)
        schema = scls.update(schema, props)

        for op in self.get_subcommands(type=sd.ObjectCommand):
            schema, _ = op.apply(schema, context)

        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base = schema.get(default_base_name)
            if bases == [default_base]:
                bases = []

        removed_bases = {b.get_name(schema) for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref.get_name(schema)]

            bases[idx:idx] = [
                self.get_object(schema, context, name=b.get_name(schema))
                for b in new_bases if b.get_name(schema) not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases:
            default = metaclass.get_default_base_name()
            if default:
                bases = [self.get_object(schema, context, name=default)]
            else:
                bases = []

        bases = so.ObjectList.create(schema, bases)
        schema = scls.set_field_value(schema, 'bases', bases)
        new_ancestors = compute_ancestors(schema, scls)
        new_ancestors = so.ObjectList.create(schema, new_ancestors)
        schema = scls.set_field_value(schema, 'ancestors', new_ancestors)

        if not self.has_attribute_value('bases'):
            self.set_attribute_value('bases', bases)

        if not self.has_attribute_value('ancestors'):
            self.set_attribute_value('ancestors', new_ancestors)

        alter_cmd = sd.ObjectCommandMeta.get_command_class(
            sd.AlterObject, metaclass)

        descendants = list(scls.descendants(schema))
        if descendants and not list(self.get_subcommands(type=alter_cmd)):
            for descendant in descendants:
                new_ancestors = compute_ancestors(schema, descendant)
                new_ancestors = so.ObjectList.create(schema, new_ancestors)
                schema = descendant.set_field_value(schema, 'ancestors',
                                                    new_ancestors)
                alter = alter_cmd(classname=descendant.get_name(schema))
                alter.add(
                    sd.AlterObjectProperty(
                        property='ancestors',
                        new_value=new_ancestors,
                    ))
                self.add(alter)

        schema = scls.acquire_ancestor_inheritance(schema)

        return schema, scls
Ejemplo n.º 12
0
class ObjectCommand(Command, metaclass=ObjectCommandMeta):
    """Base class for all Object-related commands."""
    classname = struct.Field(sn.Name)

    @classmethod
    def _get_ast_name(cls, schema, astnode, context):
        return astnode.name.name

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        nqname = cls._get_ast_name(schema, astnode, context)
        module = context.modaliases.get(astnode.name.module,
                                        astnode.name.module)
        if module is None:
            raise errors.SchemaDefinitionError(
                f'unqualified name and no default module set',
                context=astnode.name.context)

        return sn.Name(module=module, name=nqname)

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        classname = cls._classname_from_ast(schema, astnode, context)
        return cls(classname=classname)

    def _build_alter_cmd_stack(self, schema, context, scls, *, referrer=None):
        root = DeltaRoot()
        return root, root

    def _prohibit_if_expr_refs(self, schema, context, action):
        scls = self.scls
        expr_refs = s_expr.get_expr_referrers(schema, scls)

        if expr_refs:
            ref_desc = []
            for ref, fn in expr_refs.items():
                if fn == 'expr':
                    fdesc = 'expression'
                else:
                    fdesc = f"{fn.replace('_', ' ')} expression"

                vn = ref.get_verbosename(schema, with_parent=True)

                ref_desc.append(f'{fdesc} of {vn}')

            expr_s = 'an expression' if len(ref_desc) == 1 else 'expressions'
            ref_desc_s = "\n - " + "\n - ".join(ref_desc)

            raise errors.SchemaDefinitionError(
                f'cannot {action} because it is used in {expr_s}',
                details=(
                    f'{scls.get_verbosename(schema)} is used in:{ref_desc_s}'))

    def _append_subcmd_ast(cls, schema, node, subcmd, context):
        subnode = subcmd.get_ast(schema, context)
        if subnode is not None:
            node.commands.append(subnode)

    def _get_ast_node(self, schema, context):
        return self.__class__.astnode

    def _get_ast(self, schema, context):
        astnode = self._get_ast_node(schema, context)
        if isinstance(self.classname, sn.Name):
            nname = sn.shortname_from_fullname(self.classname)
            name = qlast.ObjectRef(module=nname.module, name=nname.name)
        else:
            name = qlast.ObjectRef(module='', name=self.classname)

        if astnode.get_field('name'):
            op = astnode(name=name)
        else:
            op = astnode()

        self._apply_fields_ast(schema, context, op)

        return op

    def _apply_fields_ast(self, schema, context, node):
        for op in self.get_subcommands(type=RenameObject):
            self._append_subcmd_ast(schema, node, op, context)

        mcls = self.get_schema_metaclass()

        for op in self.get_subcommands(type=AlterObjectProperty):
            self._apply_field_ast(schema, context, node, op)

        for refdict in mcls.get_refdicts():
            self._apply_refs_fields_ast(schema, context, node, refdict)

    def _apply_refs_fields_ast(self, schema, context, node, refdict):
        for op in self.get_subcommands(metaclass=refdict.ref_cls):
            self._append_subcmd_ast(schema, node, op, context)

    def _apply_field_ast(self, schema, context, node, op):
        if op.property != 'name':
            subnode = op._get_ast(schema, context)
            if subnode is not None:
                node.commands.append(subnode)

    @classmethod
    def get_schema_metaclass(cls):
        if cls._schema_metaclass is None:
            raise TypeError(f'schema metaclass not set for {cls}')
        return cls._schema_metaclass

    def get_subcommands(self, *, type=None, metaclass=None):
        if metaclass is not None:
            return filter(
                lambda i: (isinstance(i, ObjectCommand) and issubclass(
                    i.get_schema_metaclass(), metaclass)), self)
        else:
            return super().get_subcommands(type=type)

    def _validate_legal_command(self, schema, context):
        from . import functions as s_functions

        if (not context.stdmode and not context.testmode
                and not isinstance(self, s_functions.ParameterCommand)):

            if isinstance(self.classname, sn.Name):
                shortname = sn.shortname_from_fullname(self.classname)
                modname = self.classname.module
            else:
                # modules have classname as simple strings
                shortname = modname = self.classname

            if modname in s_schema.STD_MODULES:
                raise errors.SchemaDefinitionError(
                    f'cannot {self._delta_action} `{shortname}`: '
                    f'module {modname} is read-only',
                    context=self.source_context)

    def get_object(self, schema, context, *, name=None):
        if name is None:
            name = self.classname
            rename = context.renames.get(name)
            if rename is not None:
                name = rename
        metaclass = self.get_schema_metaclass()
        return schema.get(name, type=metaclass)

    def compute_inherited_fields(self, schema, context):
        result = {}
        for op in self.get_subcommands(type=AlterObjectProperty):
            result[op.property] = op.source == 'inheritance'

        return immu.Map(result)

    def _prepare_field_updates(self, schema, context):
        result = {}
        metaclass = self.get_schema_metaclass()

        for op in self.get_subcommands(type=AlterObjectProperty):
            field = metaclass.get_field(op.property)
            if field is None:
                raise errors.SchemaDefinitionError(
                    f'got AlterObjectProperty command for '
                    f'invalid field: {metaclass.__name__}.{op.property}')

            val = self._resolve_attr_value(op.new_value, op.property, field,
                                           schema)

            if isinstance(val, s_expr.Expression) and not val.is_compiled():
                val = self.compile_expr_field(schema, context, field, val)

            result[op.property] = val

        return schema, result

    def _get_field_updates(self, schema, context):
        field_updates = context.get_cached((self, 'field_updates'))
        if field_updates is None or True:
            schema, field_updates = self._prepare_field_updates(
                schema, context)
            context.cache_value((self, 'field_updates'), field_updates)

        return schema, field_updates

    def compile_expr_field(self, schema, context, field, value):
        cdn = self.get_schema_metaclass().get_schema_class_displayname()
        raise errors.InternalServerError(
            f'uncompiled expression in the field {field.name!r} of '
            f'{cdn} {self.classname!r}')
Ejemplo n.º 13
0
class RebaseInheritingObject(
        AlterInheritingObjectFragment[so.InheritingObjectT], ):
    _delta_action = 'rebase'

    removed_bases = struct.Field(tuple)  # type: ignore
    added_bases = struct.Field(tuple)  # type: ignore

    def __repr__(self) -> str:
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__, self.classname)

    def apply(self, schema: s_schema.Schema,
              context: sd.CommandContext) -> s_schema.Schema:
        schema = super().apply(schema, context)
        scls = self.scls

        assert isinstance(scls, so.InheritingObject)

        for op in self.get_subcommands(type=sd.ObjectCommand):
            schema = op.apply(schema, context)

        if not context.canonical:
            bases = self._apply_base_delta(schema, context, scls)
            schema = scls.set_field_value(schema, 'bases', bases)
            self.set_attribute_value('bases', bases)

            schema = self._recompute_inheritance(schema, context)

            if context.enable_recursion:
                alter_cmd = sd.ObjectCommandMeta.get_command_class_or_die(
                    sd.AlterObject, type(scls))
                assert issubclass(alter_cmd, AlterInheritingObject)

                for descendant in scls.ordered_descendants(schema):
                    descendant_alter = alter_cmd(
                        classname=descendant.get_name(schema))
                    descendant_alter.scls = descendant
                    with descendant_alter.new_context(schema, context,
                                                      descendant):
                        schema = descendant_alter._recompute_inheritance(
                            schema, context)
                    self.add(descendant_alter)

        assert isinstance(scls, so.InheritingObject)

        return schema

    def _apply_base_delta(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        scls: so.InheritingObjectT,
    ) -> so.ObjectList[so.InheritingObjectT]:
        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base: Optional[so.InheritingObjectT] = self.get_object(
                schema, context, name=default_base_name)
            if bases == [default_base]:
                bases = []
        else:
            default_base = None

        removed_bases = {b.name for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref.name]

            bases[idx:idx] = [
                self.get_object(schema, context, name=b.name)
                for b in new_bases if b.name not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases and default_base:
            bases = [default_base]

        return so.ObjectList[so.InheritingObjectT].create(schema, bases)
Ejemplo n.º 14
0
class RenameObject(ObjectCommand):
    _delta_action = 'rename'

    astnode = qlast.Rename

    new_name = struct.Field(sn.Name)

    def __repr__(self):
        return '<%s.%s "%s" to "%s">' % (self.__class__.__module__,
                                         self.__class__.__name__,
                                         self.classname, self.new_name)

    def _rename_begin(self, schema, context, scls):
        self._validate_legal_command(schema, context)

        self.old_name = self.classname
        schema = scls.set_field_value(schema, 'name', self.new_name)

        parent_ctx = context.get(CommandContextToken)
        for subop in parent_ctx.op.get_subcommands(type=ObjectCommand):
            if subop is not self and subop.classname == self.old_name:
                subop.classname = self.new_name

        return schema

    def _rename_innards(self, schema, context, scls):
        return schema

    def _rename_finalize(self, schema, context, scls):
        return schema

    def apply(self, schema, context):
        parent_ctx = context.get(CommandContextToken)
        scls = self.scls = parent_ctx.op.scls

        schema = self._rename_begin(schema, context, scls)
        schema = self._rename_innards(schema, context, scls)
        schema = self._rename_finalize(schema, context, scls)

        return schema, scls

    def _get_ast(self, schema, context):
        astnode = self._get_ast_node(context)

        new_name = sn.shortname_from_fullname(self.new_name)

        if new_name != self.new_name:
            # Derived name
            name_b32 = base64.b32encode(self.new_name.name.encode()).decode()
            new_nname = '__b32_' + name_b32.replace('=', '_')

            new_name = sn.Name(module=self.new_name.module, name=new_nname)
        else:
            new_name = self.new_name

        ref = qlast.ObjectRef(name=new_name.name, module=new_name.module)
        return astnode(new_name=ref)

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        parent_ctx = context.get(CommandContextToken)
        parent_class = parent_ctx.op.get_schema_metaclass()
        rename_class = ObjectCommandMeta.get_command_class(
            RenameObject, parent_class)
        return rename_class._rename_cmd_from_ast(schema, astnode, context)

    @classmethod
    def _rename_cmd_from_ast(cls, schema, astnode, context):
        parent_ctx = context.get(CommandContextToken)
        parent_class = parent_ctx.op.get_schema_metaclass()
        rename_class = ObjectCommandMeta.get_command_class(
            RenameObject, parent_class)

        new_name = astnode.new_name
        if new_name.name.startswith('__b32_'):
            name_b32 = new_name.name[6:].replace('_', '=')
            new_nname = base64.b32decode(name_b32).decode()
            new_name = sn.Name(module=new_name.module, name=new_nname)
        else:
            new_name = cls._classname_from_ast(schema, astnode, context)

        return rename_class(metaclass=parent_class,
                            classname=parent_ctx.op.classname,
                            new_name=sn.Name(module=new_name.module,
                                             name=new_name.name))
Ejemplo n.º 15
0
class RebaseInheritingObject(
        AlterInheritingObjectFragment[so.InheritingObjectT], ):
    _delta_action = 'rebase'

    removed_bases = struct.Field(tuple)  # type: ignore
    added_bases = struct.Field(tuple)  # type: ignore

    def __repr__(self) -> str:
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__, self.classname)

    def get_verb(self) -> str:
        # FIXME: We just say 'alter' because it is currently somewhat
        # inconsistent whether an object rebase on its own will get
        # placed in its own alter command or whether it will share one
        # with all the associated rebases of pointers.  Ideally we'd
        # say 'alter base types of', but with the current machinery it
        # would still usually say 'alter', so just always do that.
        return 'alter'

    def apply(self, schema: s_schema.Schema,
              context: sd.CommandContext) -> s_schema.Schema:
        schema = super().apply(schema, context)
        scls = self.scls

        assert isinstance(scls, so.InheritingObject)

        if not context.canonical:
            bases = self._apply_base_delta(schema, context, scls)
            self.set_attribute_value(
                'bases',
                bases,
                orig_value=scls.get_bases(schema),
            )
            schema = scls.set_field_value(schema, 'bases', bases)

            schema = self._recompute_inheritance(schema, context)

            if context.enable_recursion:
                for descendant in scls.ordered_descendants(schema):
                    d_root_cmd, d_alter_cmd, ctx_stack = (
                        descendant.init_delta_branch(schema, context,
                                                     sd.AlterObject))
                    assert isinstance(d_alter_cmd, InheritingObjectCommand)
                    with ctx_stack():
                        schema = d_alter_cmd._recompute_inheritance(
                            schema, context)
                    self.add(d_root_cmd)

        assert isinstance(scls, so.InheritingObject)

        return schema

    def _apply_base_delta(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        scls: so.InheritingObjectT,
    ) -> so.ObjectList[so.InheritingObjectT]:
        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base: Optional[so.InheritingObjectT] = self.get_object(
                schema, context, name=default_base_name)
            if bases == [default_base]:
                bases = []
        else:
            default_base = None

        removed_bases = {b.name for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref.name]

            bases[idx:idx] = [
                self.get_object(schema, context, name=b.name)
                for b in new_bases if b.name not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases and default_base:
            bases = [default_base]

        return so.ObjectList[so.InheritingObjectT].create(schema, bases)

    def _get_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        *,
        parent_node: Optional[qlast.DDLOperation] = None,
    ) -> Optional[qlast.DDLOperation]:
        assert parent_node is not None

        dropped = self._get_bases_for_ast(schema, context, self.removed_bases)

        if dropped:
            parent_node.commands.append(
                qlast.AlterDropInherit(bases=[
                    cast(qlast.TypeName, utils.typeref_to_ast(schema, b))
                    for b in dropped
                ], ))

        for bases, pos in self.added_bases:
            bases = self._get_bases_for_ast(schema, context, bases)
            if not bases:
                continue

            if isinstance(pos, tuple):
                typ = utils.typeref_to_ast(schema, pos[1])
                assert isinstance(typ, qlast.TypeName)

                assert isinstance(typ.maintype, qlast.ObjectRef)
                pos_node = qlast.Position(
                    position=pos[0],
                    ref=typ.maintype,
                )

            else:
                pos_node = qlast.Position(position=pos)

            parent_node.commands.append(
                qlast.AlterAddInherit(
                    bases=[
                        cast(qlast.TypeName, utils.typeref_to_ast(schema, b))
                        for b in bases
                    ],
                    position=pos_node,
                ))

        return None

    def _get_bases_for_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        bases: Tuple[so.ObjectShell[so.InheritingObjectT], ...],
    ) -> Tuple[so.ObjectShell[so.InheritingObjectT], ...]:
        mcls = self.get_schema_metaclass()
        roots = set(mcls.get_root_classes())
        return tuple(b for b in bases if b.name not in roots)
Ejemplo n.º 16
0
class RebaseInheritingObject(InheritingObjectCommand):
    _delta_action = 'rebase'

    removed_bases = struct.Field(tuple)
    added_bases = struct.Field(tuple)

    def __repr__(self):
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__,
                                 self.classname)

    def apply(self, schema, context):
        scls = self.get_object(schema, context)
        self.scls = scls

        schema, props = self._get_field_updates(schema, context)
        schema = scls.update(schema, props)

        for op in self.get_subcommands(type=sd.ObjectCommand):
            schema, _ = op.apply(schema, context)

        if not context.canonical:
            bases = self._apply_base_delta(schema, context, scls)
            schema = scls.set_field_value(schema, 'bases', bases)
            self.set_attribute_value('bases', bases)

            schema = self._recompute_inheritance(schema, context)

            if context.enable_recursion:
                alter_cmd = sd.ObjectCommandMeta.get_command_class(
                    sd.AlterObject, type(scls))

                for descendant in scls.ordered_descendants(schema):
                    descendant_alter = alter_cmd(
                        classname=descendant.get_name(schema))
                    descendant_alter.scls = descendant
                    with descendant_alter.new_context(
                            schema, context, descendant):
                        schema = descendant_alter._recompute_inheritance(
                            schema, context)
                    self.add(descendant_alter)

        return schema, scls

    def _apply_base_delta(self, schema, context, scls):
        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base = self.get_object(
                schema, context, name=default_base_name)
            if bases == [default_base]:
                bases = []
        else:
            default_base = None

        removed_bases = {b.get_name(schema) for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref.get_name(schema)]

            bases[idx:idx] = [
                self.get_object(schema, context, name=b.get_name(schema))
                for b in new_bases if b.get_name(schema) not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases and default_base:
            bases = [default_base]

        return so.ObjectList.create(schema, bases)
Ejemplo n.º 17
0
class ReferencedInheritingObjectCommand(
        ReferencedObjectCommand[ReferencedInheritingObjectT],
        inheriting.InheritingObjectCommand[ReferencedInheritingObjectT],
):

    ref_op_propagated = struct.Field(bool, default=False)

    def _get_implicit_ref_bases(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        referrer: so.InheritingObject,
        referrer_field: str,
        fq_name: sn.SchemaName,
    ) -> List[ReferencedInheritingObjectT]:

        assert isinstance(referrer, so.QualifiedObject)
        child_referrer_bases = referrer.get_bases(schema).objects(schema)
        implicit_bases = []
        ref_field_type = type(referrer).get_field(referrer_field).type

        for ref_base in child_referrer_bases:
            fq_name_in_child = self._classname_from_name(
                fq_name, ref_base.get_name(schema))
            refname = ref_field_type.get_key_for_name(schema, fq_name_in_child)
            parent_coll = ref_base.get_field_value(schema, referrer_field)
            parent_item = parent_coll.get(schema, refname, default=None)
            if (parent_item is not None
                    and not parent_item.get_is_final(schema)):
                implicit_bases.append(parent_item)

        return implicit_bases

    def get_ref_implicit_base_delta(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        refcls: ReferencedInheritingObjectT,
        implicit_bases: List[ReferencedInheritingObjectT],
    ) -> inheriting.BaseDelta_T:
        child_bases = refcls.get_bases(schema).objects(schema)

        default_base = refcls.get_default_base_name()
        explicit_bases = [
            b for b in child_bases
            if b.generic(schema) and b.get_name(schema) != default_base
        ]

        new_bases = implicit_bases + explicit_bases
        return inheriting.delta_bases(
            [b.get_name(schema) for b in child_bases],
            [b.get_name(schema) for b in new_bases],
        )

    def _validate(self, schema: s_schema.Schema,
                  context: sd.CommandContext) -> None:
        scls = self.scls
        implicit_bases = [
            b for b in scls.get_bases(schema).objects(schema)
            if not b.generic(schema)
        ]

        referrer_ctx = self.get_referrer_context_or_die(context)
        objcls = self.get_schema_metaclass()
        referrer_class = referrer_ctx.op.get_schema_metaclass()
        refdict = referrer_class.get_refdict_for_class(objcls)

        if context.declarative and scls.get_is_owned(schema):
            if (implicit_bases and refdict.requires_explicit_overloaded
                    and not self.get_attribute_value('declared_overloaded')):

                ancestry = []

                for obj in implicit_bases:
                    bref = obj.get_referrer(schema)
                    assert bref is not None
                    ancestry.append(bref)

                raise errors.SchemaDefinitionError(
                    f'{self.scls.get_verbosename(schema, with_parent=True)} '
                    f'must be declared using the `overloaded` keyword because '
                    f'it is defined in the following ancestor(s): '
                    f'{", ".join(a.get_shortname(schema) for a in ancestry)}',
                    context=self.source_context,
                )
            elif (not implicit_bases
                  and self.get_attribute_value('declared_overloaded')):

                raise errors.SchemaDefinitionError(
                    f'{self.scls.get_verbosename(schema, with_parent=True)}: '
                    f'cannot be declared `overloaded` as there are no '
                    f'ancestors defining it.',
                    context=self.source_context,
                )

    def get_implicit_bases(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        bases: Any,
    ) -> Sequence[str]:

        mcls = self.get_schema_metaclass()
        default_base = mcls.get_default_base_name()

        if isinstance(bases, so.ObjectCollectionShell):
            base_names = [b.get_name(schema) for b in bases.items]
        elif isinstance(bases, so.ObjectList):
            base_names = list(bases.names(schema))
        else:
            # assume regular iterable of shells
            base_names = [b.get_name(schema) for b in bases]

        # Filter out explicit bases
        implicit_bases = [
            b for b in base_names
            if (b != default_base and isinstance(b, sn.SchemaName)
                and sn.shortname_from_fullname(b) != b)
        ]

        return implicit_bases

    def _propagate_ref_op(
        self, schema: s_schema.Schema, context: sd.CommandContext,
        scls: ReferencedInheritingObject,
        cb: Callable[[sd.ObjectCommand[so.Object], str],
                     None]) -> s_schema.Schema:
        for ctx in reversed(context.stack):
            if (isinstance(ctx.op, ReferencedInheritingObjectCommand)
                    and ctx.op.ref_op_propagated):
                return schema

        referrer_ctx = self.get_referrer_context_or_die(context)
        referrer = referrer_ctx.scls
        referrer_class = type(referrer)
        mcls = type(scls)
        refdict = referrer_class.get_refdict_for_class(mcls)
        reftype = referrer_class.get_field(refdict.attr).type
        refname = reftype.get_key_for(schema, self.scls)

        for descendant in scls.ordered_descendants(schema):
            d_alter_cmd = descendant.init_delta_command(schema, sd.AlterObject)
            assert isinstance(descendant, ReferencedObject)
            d_alter_cmd.ref_op_propagated = True
            d_referrer = descendant.get_referrer(schema)
            assert d_referrer is not None
            r_alter_cmd = d_referrer.init_delta_command(schema, sd.AlterObject)

            with r_alter_cmd.new_context(schema, context, d_referrer):
                with d_alter_cmd.new_context(schema, context, descendant):
                    cb(d_alter_cmd, refname)  # type: ignore

                r_alter_cmd.add(d_alter_cmd)

            self.add(r_alter_cmd)

        return schema

    def _drop_owned_refs(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        refdict: so.RefDict,
    ) -> s_schema.Schema:

        scls = self.scls
        refs = scls.get_field_value(schema, refdict.attr)

        for ref in refs.objects(schema):
            inherited = ref.get_implicit_bases(schema)
            if inherited and ref.get_is_owned(schema):
                drop_owned = ref.init_delta_command(schema, AlterOwned)
                drop_owned.set_attribute_value('is_owned', False)
                alter = ref.init_delta_command(schema, sd.AlterObject)
                alter.add(drop_owned)
                schema = alter.apply(schema, context)
                self.add(alter)
            else:
                drop_ref = ref.init_delta_command(schema, sd.DeleteObject)
                self.add(drop_ref)

        return schema
Ejemplo n.º 18
0
class ObjectCommand(Command, metaclass=ObjectCommandMeta):
    """Base class for all Object-related commands."""
    classname = struct.Field(sn.Name)

    @classmethod
    def _get_ast_name(cls, schema, astnode, context):
        return astnode.name.name

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        nqname = cls._get_ast_name(schema, astnode, context)
        module = context.modaliases.get(astnode.name.module,
                                        astnode.name.module)
        if module is None:
            raise errors.SchemaDefinitionError(
                f'unqualified name and no default module set',
                context=astnode.name.context
            )

        return sn.Name(module=module, name=nqname)

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        classname = cls._classname_from_ast(schema, astnode, context)
        return cls(classname=classname)

    def _append_subcmd_ast(cls, schema, node, subcmd, context):
        subnode = subcmd.get_ast(schema, context)
        if subnode is not None:
            node.commands.append(subnode)

    def _get_ast_node(self, context):
        return self.__class__.astnode

    def _get_ast(self, schema, context):
        astnode = self._get_ast_node(context)
        if isinstance(self.classname, sn.Name):
            nname = sn.shortname_from_fullname(self.classname)
            name = qlast.ObjectRef(module=nname.module, name=nname.name)
        else:
            name = qlast.ObjectRef(module='', name=self.classname)

        if astnode.get_field('name'):
            op = astnode(name=name)
        else:
            op = astnode()

        self._apply_fields_ast(schema, context, op)

        return op

    def _apply_fields_ast(self, schema, context, node):
        for op in self.get_subcommands(type=RenameObject):
            self._append_subcmd_ast(schema, node, op, context)

        for op in self.get_subcommands(type=AlterObjectProperty):
            self._apply_field_ast(schema, context, node, op)

        mcls = self.get_schema_metaclass()
        for refdict in mcls.get_refdicts():
            self._apply_refs_fields_ast(schema, context, node, refdict)

    def _apply_refs_fields_ast(self, schema, context, node, refdict):
        for op in self.get_subcommands(metaclass=refdict.ref_cls):
            self._append_subcmd_ast(schema, node, op, context)

    def _apply_field_ast(self, schema, context, node, op):
        if op.property == 'name':
            pass
        else:
            subnode = op._get_ast(schema, context)
            if subnode is not None:
                node.commands.append(subnode)

    @classmethod
    def get_schema_metaclass(cls):
        if cls._schema_metaclass is None:
            raise TypeError(f'schema metaclass not set for {cls}')
        return cls._schema_metaclass

    def get_subcommands(self, *, type=None, metaclass=None):
        if metaclass is not None:
            return filter(
                lambda i: (isinstance(i, ObjectCommand) and
                           issubclass(i.get_schema_metaclass(), metaclass)),
                self)
        else:
            return super().get_subcommands(type=type)

    def _validate_legal_command(self, schema, context):
        from . import functions as s_functions

        if (not context.stdmode and not context.testmode and
                not isinstance(self, s_functions.ParameterCommand)):

            if isinstance(self.classname, sn.Name):
                shortname = sn.shortname_from_fullname(self.classname)
                modname = self.classname.module
            else:
                # modules have classname as simple strings
                shortname = modname = self.classname

            if modname in s_schema.STD_MODULES:
                raise errors.SchemaDefinitionError(
                    f'cannot {self._delta_action} `{shortname}`: '
                    f'module {modname} is read-only',
                    context=self.source_context)

    def get_object(self, schema, *, name=None):
        if name is None:
            name = self.classname
        metaclass = self.get_schema_metaclass()
        return schema.get(name, type=metaclass)

    def _prepare_field_updates(self, schema, context):
        result = {}
        metaclass = self.get_schema_metaclass()

        for op in self.get_subcommands(type=AlterObjectProperty):
            field = metaclass.get_field(op.property)
            if field is None:
                raise errors.SchemaDefinitionError(
                    f'got AlterObjectProperty command for '
                    f'invalid field: {metaclass.__name__}.{op.property}')

            val = self._resolve_attr_value(
                op.new_value, op.property, field, schema)

            if isinstance(val, s_expr.Expression) and not val.is_compiled():
                val = self.compile_expr_field(schema, context, field, val)

            result[op.property] = val

        self._field_updates = result

        return schema, result

    def _get_field_updates(self, schema, context):
        field_updates = context.get_cached((self, 'field_updates'))
        if field_updates is None:
            schema, field_updates = self._prepare_field_updates(
                schema, context)
            context.cache_value((self, 'field_updates'), field_updates)

        return schema, field_updates

    def compile_expr_field(self, schema, context, field, value):
        cdn = self.get_schema_metaclass().get_schema_class_displayname()
        raise errors.InternalServerError(
            f'uncompiled expression in the field {field.name!r} of '
            f'{cdn} {self.classname!r}'
        )
Ejemplo n.º 19
0
class ObjectCommand(Command, metaclass=ObjectCommandMeta):
    """Base class for all Object-related commands."""
    classname = struct.Field(sn.Name)

    @classmethod
    def _get_ast_name(cls, schema, astnode, context):
        return astnode.name.name

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        nqname = cls._get_ast_name(schema, astnode, context)
        module = context.modaliases.get(astnode.name.module,
                                        astnode.name.module)
        if module is None:
            raise errors.SchemaDefinitionError(
                f'unqualified name and no default module set',
                context=astnode.name.context)

        return sn.Name(module=module, name=nqname)

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        classname = cls._classname_from_ast(schema, astnode, context)
        return cls(classname=classname)

    def _append_subcmd_ast(cls, schema, node, subcmd, context):
        subnode = subcmd.get_ast(schema, context)
        if subnode is not None:
            node.commands.append(subnode)

    def _get_ast_node(self, context):
        return self.__class__.astnode

    def _get_ast(self, schema, context):
        astnode = self._get_ast_node(context)
        if isinstance(self.classname, sn.Name):
            nname = sn.shortname_from_fullname(self.classname)
            name = qlast.ObjectRef(module=nname.module, name=nname.name)
        else:
            name = qlast.ObjectRef(module='', name=self.classname)

        if astnode.get_field('name'):
            op = astnode(name=name)
        else:
            op = astnode()

        self._apply_fields_ast(schema, context, op)

        return op

    def _apply_fields_ast(self, schema, context, node):
        for op in self.get_subcommands(type=RenameObject):
            self._append_subcmd_ast(schema, node, op, context)

        for op in self.get_subcommands(type=AlterObjectProperty):
            self._apply_field_ast(schema, context, node, op)

        mcls = self.get_schema_metaclass()
        for refdict in mcls.get_refdicts():
            self._apply_refs_fields_ast(schema, context, node, refdict)

    def _apply_refs_fields_ast(self, schema, context, node, refdict):
        for op in self.get_subcommands(metaclass=refdict.ref_cls):
            self._append_subcmd_ast(schema, node, op, context)

    def _apply_field_ast(self, schema, context, node, op):
        if op.property == 'name':
            pass
        else:
            subnode = op._get_ast(schema, context)
            if subnode is not None:
                node.commands.append(subnode)

    @classmethod
    def get_schema_metaclass(cls):
        if cls._schema_metaclass is None:
            raise TypeError(f'schema metaclass not set for {cls}')
        return cls._schema_metaclass

    def get_subcommands(self, *, type=None, metaclass=None):
        if metaclass is not None:
            return filter(
                lambda i: (isinstance(i, ObjectCommand) and issubclass(
                    i.get_schema_metaclass(), metaclass)), self)
        else:
            return super().get_subcommands(type=type)

    def _validate_legal_command(self, schema, context):
        from . import functions as s_functions

        if (not context.stdmode and not context.testmode
                and not isinstance(self, s_functions.ParameterCommand)):

            if isinstance(self.classname, sn.Name):
                shortname = sn.shortname_from_fullname(self.classname)
                modname = self.classname.module
            else:
                # modules have classname as simple strings
                shortname = modname = self.classname

            if modname in s_schema.STD_MODULES:
                raise errors.SchemaDefinitionError(
                    f'cannot {self._delta_action} `{shortname}`: '
                    f'module {modname} is read-only',
                    context=self.source_context)

    def get_object(self, schema, *, name=None):
        if name is None:
            name = self.classname
        metaclass = self.get_schema_metaclass()
        return schema.get(name, type=metaclass)
Ejemplo n.º 20
0
class Token(BaseCode):
    val = struct.Field(str)
Ejemplo n.º 21
0
class RenameObject(AlterObjectFragment):
    _delta_action = 'rename'

    astnode = qlast.Rename

    new_name = struct.Field(sn.Name)

    def __repr__(self):
        return '<%s.%s "%s" to "%s">' % (self.__class__.__module__,
                                         self.__class__.__name__,
                                         self.classname, self.new_name)

    def _rename_begin(self, schema, context, scls):
        self._validate_legal_command(schema, context)

        # Renames of schema objects used in expressions is
        # not supported yet.  Eventually we'll add support
        # for transparent recompilation.
        vn = scls.get_verbosename(schema)
        self._prohibit_if_expr_refs(schema, context, action=f'rename {vn}')

        self.old_name = self.classname
        schema = scls.set_field_value(schema, 'name', self.new_name)

        return schema

    def _rename_innards(self, schema, context, scls):
        return schema

    def _rename_finalize(self, schema, context, scls):
        return schema

    def apply(self, schema, context):
        parent_ctx = context.current()
        scls = self.scls = parent_ctx.op.scls

        context.renames[self.classname] = self.new_name
        context.renamed_objs.add(scls)

        schema = self._rename_begin(schema, context, scls)
        schema = self._rename_innards(schema, context, scls)
        schema = self._rename_finalize(schema, context, scls)

        return schema, scls

    def _get_ast(self, schema, context):
        astnode = self._get_ast_node(schema, context)

        new_name = sn.shortname_from_fullname(self.new_name)

        if new_name != self.new_name:
            # Derived name
            name_b32 = base64.b32encode(self.new_name.name.encode()).decode()
            new_nname = '__b32_' + name_b32.replace('=', '_')

            new_name = sn.Name(module=self.new_name.module, name=new_nname)
        else:
            new_name = self.new_name

        ref = qlast.ObjectRef(name=new_name.name, module=new_name.module)
        return astnode(new_name=ref)

    @classmethod
    def _cmd_from_ast(cls, schema, astnode, context):
        parent_ctx = context.current()
        parent_class = parent_ctx.op.get_schema_metaclass()
        rename_class = ObjectCommandMeta.get_command_class(
            RenameObject, parent_class)
        return rename_class._rename_cmd_from_ast(schema, astnode, context)

    @classmethod
    def _rename_cmd_from_ast(cls, schema, astnode, context):
        parent_ctx = context.current()
        parent_class = parent_ctx.op.get_schema_metaclass()
        rename_class = ObjectCommandMeta.get_command_class(
            RenameObject, parent_class)

        new_name = astnode.new_name
        if new_name.name.startswith('__b32_'):
            name_b32 = new_name.name[6:].replace('_', '=')
            new_nname = base64.b32decode(name_b32).decode()
            new_name = sn.Name(module=new_name.module, name=new_nname)
        else:
            new_name = cls._classname_from_ast(schema, astnode, context)

        return rename_class(metaclass=parent_class,
                            classname=parent_ctx.op.classname,
                            new_name=sn.Name(module=new_name.module,
                                             name=new_name.name))
Ejemplo n.º 22
0
class RebaseInheritingObject(sd.ObjectCommand):
    _delta_action = 'rebase'

    new_base = struct.Field(tuple, default=tuple())
    removed_bases = struct.Field(tuple)
    added_bases = struct.Field(tuple)

    def __repr__(self):
        return '<%s.%s "%s">' % (self.__class__.__module__,
                                 self.__class__.__name__, self.classname)

    def apply(self, schema, context):
        metaclass = self.get_schema_metaclass()
        scls = self.get_object(schema)
        self.scls = scls

        objects = [scls] + list(scls.descendants(schema))
        for obj in objects:
            for refdict in scls.__class__.get_refdicts():
                attr = refdict.attr
                local_attr = refdict.local_attr
                backref = refdict.backref_attr

                coll = obj.get_field_value(schema, attr)
                local_coll = obj.get_field_value(schema, local_attr)

                for ref_name in tuple(coll.keys(schema)):
                    if not local_coll.has(schema, ref_name):
                        try:
                            obj.get_classref_origin(schema, ref_name, attr,
                                                    local_attr, backref)
                        except KeyError:
                            schema, coll = coll.delete(schema, [ref_name])

        schema, props = self._get_field_updates(schema, context)
        schema = scls.update(schema, props)

        for op in self.get_subcommands(type=sd.ObjectCommand):
            schema, _ = op.apply(schema, context)

        bases = list(scls.get_bases(schema).objects(schema))
        default_base_name = scls.get_default_base_name()
        if default_base_name:
            default_base = schema.get(default_base_name)
            if bases == [default_base]:
                bases = []

        removed_bases = {b.get_name(schema) for b in self.removed_bases}
        existing_bases = set()

        for b in bases:
            if b.get_name(schema) in removed_bases:
                bases.remove(b)
            else:
                existing_bases.add(b.get_name(schema))

        index = {b.get_name(schema): i for i, b in enumerate(bases)}

        for new_bases, pos in self.added_bases:
            if isinstance(pos, tuple):
                pos, ref = pos

            if pos is None or pos == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref]

            bases[idx:idx] = [
                self.get_object(schema, name=b.get_name(schema))
                for b in new_bases if b.get_name(schema) not in existing_bases
            ]
            index = {b.get_name(schema): i for i, b in enumerate(bases)}

        if not bases:
            default = metaclass.get_default_base_name()
            if default:
                bases = [self.get_object(schema, name=default)]
            else:
                bases = []

        schema = scls.set_field_value(schema, 'bases', bases)
        new_ancestors = compute_mro(schema, scls)[1:]
        schema = scls.set_field_value(schema, 'ancestors', new_ancestors)

        if not self.has_attribute_value('bases'):
            self.set_attribute_value('bases', bases)

        if not self.has_attribute_value('ancestors'):
            self.set_attribute_value('ancestors', new_ancestors)

        alter_cmd = sd.ObjectCommandMeta.get_command_class(
            sd.AlterObject, metaclass)

        descendants = list(scls.descendants(schema))
        if descendants and not list(self.get_subcommands(type=alter_cmd)):
            for descendant in descendants:
                new_ancestors = compute_mro(schema, descendant)[1:]
                schema = descendant.set_field_value(schema, 'ancestors',
                                                    new_ancestors)
                alter = alter_cmd(classname=descendant.get_name(schema))
                alter.add(
                    sd.AlterObjectProperty(
                        property='ancestors',
                        new_value=new_ancestors,
                    ))
                self.add(alter)

        return schema, scls
Ejemplo n.º 23
0
class Expression(struct.MixedStruct, s_abc.ObjectContainer):
    text = struct.Field(str, frozen=True)
    origtext = struct.Field(str, default=None, frozen=True)
    refs = struct.Field(so.ObjectSet, coerce=True, default=None, frozen=True)

    def __init__(self, *args, _qlast=None, _irast=None, **kwargs):
        super().__init__(*args, **kwargs)
        self._qlast = _qlast
        self._irast = _irast

    def __getstate__(self):
        return {
            'text': self.text,
            'origtext': self.origtext,
            'refs': self.refs,
            '_qlast': None,
            '_irast': None,
        }

    @property
    def qlast(self):
        if self._qlast is None:
            self._qlast = qlparser.parse_fragment(self.text)
        return self._qlast

    @property
    def irast(self):
        return self._irast

    def is_compiled(self) -> bool:
        return self.refs is not None

    @classmethod
    def compare_values(cls, ours, theirs, *, our_schema, their_schema, context,
                       compcoef):
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef
        elif ours.text == theirs.text:
            return 1.0
        else:
            return compcoef

    @classmethod
    def from_ast(cls, qltree, schema, modaliases):
        orig_text = qlcodegen.generate_source(qltree, pretty=False)
        norm_tree = imprint_expr_context(qltree, modaliases)
        norm_text = qlcodegen.generate_source(norm_tree, pretty=False)

        return cls(
            text=norm_text,
            origtext=orig_text,
            _qlast=norm_tree,
        )

    @classmethod
    def compiled(cls,
                 expr,
                 schema,
                 *,
                 modaliases=None,
                 parent_object_type=None,
                 anchors=None,
                 path_prefix_anchor=None,
                 allow_generic_type_output=False,
                 func_params=None,
                 singletons=None) -> Expression:

        from edb.edgeql import compiler as qlcompiler

        ir = qlcompiler.compile_ast_to_ir(
            expr.qlast,
            schema=schema,
            modaliases=modaliases,
            anchors=anchors,
            path_prefix_anchor=path_prefix_anchor,
            func_params=func_params,
            parent_object_type=parent_object_type,
            allow_generic_type_output=allow_generic_type_output,
            singletons=singletons,
        )

        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_ir(cls, expr, ir, schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_expr(cls, expr, schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=(so.ObjectSet.create(schema, expr.refs.objects(schema))
                  if expr.refs is not None else None),
            _qlast=expr._qlast,
            _irast=expr._irast,
        )

    def _reduce_to_ref(self, schema):
        return type(self)(
            text=self.text,
            origtext=self.origtext,
            refs=so.ObjectSet.create(schema,
                                     (scls._reduce_to_ref(schema)[0]
                                      for scls in self.refs.objects(schema)))
            if self.refs is not None else None), self
Ejemplo n.º 24
0
class Expression(struct.MixedStruct, s_abc.ObjectContainer, s_abc.Expression):
    text = struct.Field(str, frozen=True)
    origtext = struct.Field(str, default=None, frozen=True)
    # mypy wants an argument to the ObjectSet generic, but
    # that wouldn't work for struct.Field, since subscripted
    # generics are not types.
    refs = struct.Field(
        so.ObjectSet,  # type: ignore
        coerce=True,
        default=None,
        frozen=True,
    )

    def __init__(self,
                 *args: Any,
                 _qlast: Optional[qlast_.Base] = None,
                 _irast: Optional[irast_.Command] = None,
                 **kwargs: Any):
        super().__init__(*args, **kwargs)
        self._qlast = _qlast
        self._irast = _irast

    def __getstate__(self) -> Dict[str, Any]:
        return {
            'text': self.text,
            'origtext': self.origtext,
            'refs': self.refs,
            '_qlast': None,
            '_irast': None,
        }

    @property
    def qlast(self) -> qlast_.Base:
        if self._qlast is None:
            self._qlast = qlparser.parse_fragment(self.text)
        return self._qlast

    @property
    def irast(self) -> Optional[irast_.Command]:
        return self._irast

    def is_compiled(self) -> bool:
        return self.refs is not None

    @classmethod
    def compare_values(cls: Type[Expression],
                       ours: Expression,
                       theirs: Expression,
                       *,
                       our_schema: s_schema.Schema,
                       their_schema: s_schema.Schema,
                       context: Any,
                       compcoef: float) -> float:
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef
        elif ours.text == theirs.text:
            return 1.0
        else:
            return compcoef

    @classmethod
    def from_ast(
        cls: Type[Expression],
        qltree: qlast_.Base,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        *,
        as_fragment: bool = False,
        orig_text: Optional[str] = None,
    ) -> Expression:
        if orig_text is None:
            orig_text = qlcodegen.generate_source(qltree, pretty=False)
        if not as_fragment:
            qltree = imprint_expr_context(qltree, modaliases)
        norm_text = qlcodegen.generate_source(qltree, pretty=False)

        return cls(
            text=norm_text,
            origtext=orig_text,
            _qlast=qltree,
        )

    @classmethod
    def compiled(
        cls: Type[Expression],
        expr: Expression,
        schema: s_schema.Schema,
        *,
        as_fragment: bool = False,
        modaliases: Optional[Mapping[Optional[str], str]] = None,
        parent_object_type: Optional[so.ObjectMeta] = None,
        anchors: Optional[
            Mapping[Union[str, qlast_.SpecialAnchorT], so.Object]
        ] = None,
        path_prefix_anchor: Optional[qlast_.SpecialAnchorT] = None,
        allow_generic_type_output: bool = False,
        func_params: Optional[s_func.ParameterLikeList] = None,
        singletons: Sequence[s_types.Type] = (),
    ) -> Expression:

        from edb.edgeql import compiler as qlcompiler
        from edb.ir import ast as irast_

        if as_fragment:
            ir: irast_.Command = qlcompiler.compile_ast_fragment_to_ir(
                expr.qlast,
                schema=schema,
                modaliases=modaliases,
                anchors=anchors,
                path_prefix_anchor=path_prefix_anchor,
            )
        else:
            ir = qlcompiler.compile_ast_to_ir(
                expr.qlast,
                schema=schema,
                modaliases=modaliases,
                anchors=anchors,
                path_prefix_anchor=path_prefix_anchor,
                func_params=func_params,
                parent_object_type=parent_object_type,
                allow_generic_type_output=allow_generic_type_output,
                singletons=singletons,
            )

        assert isinstance(ir, irast_.Statement)

        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_ir(cls: Type[Expression],
                expr: Expression,
                ir: irast_.Statement,
                schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_expr(cls: Type[Expression],
                  expr: Expression,
                  schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=(
                so.ObjectSet.create(schema, expr.refs.objects(schema))
                if expr.refs is not None else None
            ),
            _qlast=expr._qlast,
            _irast=expr._irast,
        )

    def _reduce_to_ref(self,
                       schema: s_schema.Schema) -> Tuple[Expression,
                                                         Expression]:
        return type(self)(
            text=self.text,
            origtext=self.origtext,
            refs=so.ObjectSet.create(
                schema,
                (scls._reduce_to_ref(schema)[0]
                 for scls in self.refs.objects(schema))
            ) if self.refs is not None else None
        ), self
Ejemplo n.º 25
0
class Expression(struct.MixedStruct, s_abc.ObjectContainer, s_abc.Expression):
    text = struct.Field(str, frozen=True)
    origtext = struct.Field(str, default=None, frozen=True)
    # mypy wants an argument to the ObjectSet generic, but
    # that wouldn't work for struct.Field, since subscripted
    # generics are not types.
    refs = struct.Field(
        so.ObjectSet,  # type: ignore
        coerce=True,
        default=None,
        frozen=True,
    )

    def __init__(self,
                 *args: Any,
                 _qlast: Optional[qlast_.Base] = None,
                 _irast: Optional[irast_.Command] = None,
                 **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self._qlast = _qlast
        self._irast = _irast

    def __getstate__(self) -> Dict[str, Any]:
        return {
            'text': self.text,
            'origtext': self.origtext,
            'refs': self.refs,
            '_qlast': None,
            '_irast': None,
        }

    @property
    def qlast(self) -> qlast_.Base:
        if self._qlast is None:
            self._qlast = qlparser.parse_fragment(self.text)
        return self._qlast

    @property
    def irast(self) -> Optional[irast_.Command]:
        return self._irast

    def is_compiled(self) -> bool:
        return self.refs is not None

    @classmethod
    def compare_values(cls: Type[Expression], ours: Expression,
                       theirs: Expression, *, our_schema: s_schema.Schema,
                       their_schema: s_schema.Schema,
                       context: so.ComparisonContext,
                       compcoef: float) -> float:
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef
        elif ours.text == theirs.text:
            return 1.0
        else:
            return compcoef

    @classmethod
    def from_ast(
        cls: Type[Expression],
        qltree: qlast_.Base,
        schema: s_schema.Schema,
        modaliases: Mapping[Optional[str], str],
        localnames: AbstractSet[str] = frozenset(),
        *,
        as_fragment: bool = False,
        orig_text: Optional[str] = None,
    ) -> Expression:
        from edb.edgeql.compiler import normalization as qlnorm

        if orig_text is None:
            orig_text = qlcodegen.generate_source(qltree, pretty=False)
        if not as_fragment:
            qlnorm.normalize(qltree,
                             schema=schema,
                             modaliases=modaliases,
                             localnames=localnames)

        norm_text = qlcodegen.generate_source(qltree, pretty=False)

        return cls(
            text=norm_text,
            origtext=orig_text,
            _qlast=qltree,
        )

    @classmethod
    def compiled(
        cls: Type[Expression],
        expr: Expression,
        schema: s_schema.Schema,
        *,
        options: Optional[qlcompiler.CompilerOptions] = None,
        as_fragment: bool = False,
    ) -> Expression:

        from edb.ir import ast as irast_

        if as_fragment:
            ir: irast_.Command = qlcompiler.compile_ast_fragment_to_ir(
                expr.qlast,
                schema=schema,
                options=options,
            )
        else:
            ir = qlcompiler.compile_ast_to_ir(
                expr.qlast,
                schema=schema,
                options=options,
            )

        assert isinstance(ir, irast_.Statement)

        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_ir(cls: Type[Expression], expr: Expression, ir: irast_.Statement,
                schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_expr(cls: Type[Expression], expr: Expression,
                  schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            origtext=expr.origtext,
            refs=(so.ObjectSet.create(schema, expr.refs.objects(schema))
                  if expr.refs is not None else None),
            _qlast=expr._qlast,
            _irast=expr._irast,
        )

    def as_shell(self, schema: s_schema.Schema) -> ExpressionShell:
        return ExpressionShell(
            text=self.text,
            origtext=self.origtext,
            refs=(r.as_shell(schema) for r in self.refs.objects(schema))
            if self.refs is not None else None,
            _qlast=self._qlast,
        )
Ejemplo n.º 26
0
class Code(BaseCode):
    tokens = struct.Field(checked.CheckedList[Token],
                          default=None,
                          coerce=True)
Ejemplo n.º 27
0
class Expression(struct.MixedRTStruct, so.ObjectContainer, s_abc.Expression):

    text = struct.Field(str, frozen=True)
    # mypy wants an argument to the ObjectSet generic, but
    # that wouldn't work for struct.Field, since subscripted
    # generics are not types.
    refs = struct.Field(
        so.ObjectSet,  # type: ignore
        coerce=True,
        default=None,
        frozen=True,
    )

    def __init__(
        self,
        *args: Any,
        _qlast: Optional[qlast_.Expr] = None,
        _irast: Optional[irast_.Command] = None,
        **kwargs: Any
    ) -> None:
        super().__init__(*args, **kwargs)
        self._qlast = _qlast
        self._irast = _irast

    def __getstate__(self) -> Dict[str, Any]:
        return {
            'text': self.text,
            'refs': self.refs,
            '_qlast': None,
            '_irast': None,
        }

    @property
    def qlast(self) -> qlast_.Expr:
        if self._qlast is None:
            self._qlast = qlparser.parse_fragment(self.text)
        return self._qlast

    @property
    def irast(self) -> Optional[irast_.Command]:
        return self._irast

    def is_compiled(self) -> bool:
        return self.refs is not None

    def _refs_keys(self, schema: s_schema.Schema) -> Set[
            Tuple[Type[so.Object], sn.Name]]:
        return {
            (type(x), x.get_name(schema))
            for x in (self.refs.objects(schema) if self.refs else ())
        }

    @classmethod
    def compare_values(cls: Type[Expression],
                       ours: Expression,
                       theirs: Expression,
                       *,
                       our_schema: s_schema.Schema,
                       their_schema: s_schema.Schema,
                       context: so.ComparisonContext,
                       compcoef: float) -> float:
        if not ours and not theirs:
            return 1.0
        elif not ours or not theirs:
            return compcoef

        # If the new and old versions share a reference to an object
        # that is being deleted, then we must delete this object as well.
        our_refs = ours._refs_keys(our_schema)
        their_refs = theirs._refs_keys(their_schema)
        if (our_refs & their_refs) & context.deletions.keys():
            return 0.0

        if ours.text == theirs.text:
            return 1.0
        else:
            return compcoef

    @classmethod
    def from_ast(
        cls: Type[Expression],
        qltree: qlast_.Expr,
        schema: s_schema.Schema,
        modaliases: Optional[Mapping[Optional[str], str]] = None,
        localnames: AbstractSet[str] = frozenset(),
        *,
        as_fragment: bool = False,
    ) -> Expression:
        if modaliases is None:
            modaliases = {}
        if not as_fragment:
            qlcompiler.normalize(
                qltree,
                schema=schema,
                modaliases=modaliases,
                localnames=localnames
            )

        norm_text = qlcodegen.generate_source(qltree, pretty=False)

        return cls(
            text=norm_text,
            _qlast=qltree,
        )

    @classmethod
    def not_compiled(cls: Type[Expression], expr: Expression) -> Expression:
        return Expression(text=expr.text)

    @classmethod
    def compiled(
        cls: Type[Expression],
        expr: Expression,
        schema: s_schema.Schema,
        *,
        options: Optional[qlcompiler.CompilerOptions] = None,
        as_fragment: bool = False,
    ) -> Expression:

        from edb.ir import ast as irast_

        if as_fragment:
            ir: irast_.Command = qlcompiler.compile_ast_fragment_to_ir(
                expr.qlast,
                schema=schema,
                options=options,
            )
        else:
            ir = qlcompiler.compile_ast_to_ir(
                expr.qlast,
                schema=schema,
                options=options,
            )

        assert isinstance(ir, irast_.Statement)

        return cls(
            text=expr.text,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_ir(cls: Type[Expression],
                expr: Expression,
                ir: irast_.Statement,
                schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            refs=so.ObjectSet.create(schema, ir.schema_refs),
            _qlast=expr.qlast,
            _irast=ir,
        )

    @classmethod
    def from_expr(cls: Type[Expression],
                  expr: Expression,
                  schema: s_schema.Schema) -> Expression:
        return cls(
            text=expr.text,
            refs=(
                so.ObjectSet.create(schema, expr.refs.objects(schema))
                if expr.refs is not None else None
            ),
            _qlast=expr._qlast,
            _irast=expr._irast,
        )

    def as_shell(self, schema: s_schema.Schema) -> ExpressionShell:
        return ExpressionShell(
            text=self.text,
            refs=(
                r.as_shell(schema) for r in self.refs.objects(schema)
            ) if self.refs is not None else None,
            _qlast=self._qlast,
        )

    def schema_reduce(
        self,
    ) -> Tuple[
        str,
        Tuple[
            str,
            Optional[Union[Tuple[type, ...], type]],
            Tuple[uuid.UUID, ...],
            Tuple[Tuple[str, Any], ...],
        ],
    ]:
        assert self.refs is not None, 'expected expression to be compiled'
        return (
            self.text,
            self.refs.schema_reduce(),
        )

    @classmethod
    def schema_restore(
        cls,
        data: Tuple[
            str,
            Tuple[
                str,
                Optional[Union[Tuple[type, ...], type]],
                Tuple[uuid.UUID, ...],
                Tuple[Tuple[str, Any], ...],
            ],
        ],
    ) -> Expression:
        text, refs_data = data
        return Expression(
            text=text,
            refs=so.ObjectCollection.schema_restore(refs_data),
        )

    @classmethod
    def schema_refs_from_data(
        cls,
        data: Tuple[
            str,
            Tuple[
                str,
                Optional[Union[Tuple[type, ...], type]],
                Tuple[uuid.UUID, ...],
                Tuple[Tuple[str, Any], ...],
            ],
        ],
    ) -> FrozenSet[uuid.UUID]:
        return so.ObjectCollection.schema_refs_from_data(data[1])

    @property
    def ir_statement(self) -> irast_.Statement:
        """Assert this expr is a compiled EdgeQL statement and return its IR"""
        from edb.ir import ast as irast_

        if not self.is_compiled():
            raise AssertionError('expected a compiled expression')
        ir = self.irast
        if not isinstance(ir, irast_.Statement):
            raise AssertionError(
                'expected the result of an expression to be a Statement')
        return ir

    @property
    def stype(self) -> s_types.Type:
        return self.ir_statement.stype

    @property
    def cardinality(self) -> qltypes.Cardinality:
        return self.ir_statement.cardinality

    @property
    def schema(self) -> s_schema.Schema:
        return self.ir_statement.schema