Ejemplo n.º 1
0
class DatabaseCommand(sd.ObjectCommand,
                      schema_metaclass=Database,
                      context_class=DatabaseCommandContext):

    classname = struct.Field(str)

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        return astnode.name.name
Ejemplo n.º 2
0
class ModuleCommand(sd.ObjectCommand,
                    schema_metaclass=Module,
                    context_class=ModuleCommandContext):

    classname = struct.Field(str)

    @classmethod
    def _classname_from_ast(cls, schema, astnode, context):
        if astnode.name.module:
            classname = sn.Name(module=astnode.name.module,
                                name=astnode.name.name)
        else:
            classname = astnode.name.name

        return classname
Ejemplo n.º 3
0
class AlterObjectProperty(Command):
    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, astnode, context, schema):
        propname = astnode.name.name
        if astnode.name.module:
            propname = astnode.name.module + '::' + propname

        assert '::' not in propname

        if isinstance(astnode, qlast.DropAttributeValue):
            parent_ctx = context.get(CommandContextToken)
            parent_cls = parent_ctx.op.get_schema_metaclass()

            field = parent_cls._fields.get(propname)
            if (field is not None and
                    issubclass(field.type[0], typed.AbstractTypedCollection)):
                value = field.type[0]()
            else:
                value = None

            return cls(property=propname, new_value=value)

        if astnode.as_expr:
            new_value = s_expr.ExpressionText(
                edgeql.generate_source(astnode.value, pretty=False))
        else:
            if isinstance(astnode.value, qlast.Constant):
                new_value = astnode.value.value
            elif isinstance(astnode.value, qlast.Tuple):
                new_value = tuple(el.value for el in astnode.value.elements)
            else:
                raise ValueError(
                    f'unexpected value in attribute: {astnode.value!r}')

        return cls(property=propname, new_value=new_value)

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

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

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

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

        if new_value_empty and old_value_empty:
            return

        if isinstance(value, s_expr.ExpressionText):
            value = edgeql.parse(str(value))
        elif utils.is_nontrivial_container(value):
            value = qlast.Tuple(elements=[
                qlast.Constant(value=el) for el in value
            ])
        elif isinstance(value, nlang.WordCombination):
            forms = value.as_dict()
            if len(forms) > 1:
                items = []
                for k, v in forms.items():
                    items.append((
                        qlast.Constant(value=k),
                        qlast.Constant(value=v)
                    ))
                value = qlast.Array(elements=[
                    qlast.Tuple(elements=[k, v]) for k, v in items
                ])
            else:
                value = qlast.Constant(value=str(value))
        else:
            value = qlast.Constant(value=value)

        as_expr = isinstance(value, qlast.ExpressionText)
        op = qlast.CreateAttributeValue(
            name=qlast.ObjectRef(module='', name=self.property),
            value=value, as_expr=as_expr)
        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.º 4
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:
            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[0]

        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 = self.adapt_value(field, value)

        return value

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

        for op in self.get_subcommands(type=AlterObjectProperty):
            try:
                field = metaclass.get_field(op.property)
            except KeyError:
                continue

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

        return result

    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 discard_attribute(self, attr_name):
        for op in self.get_subcommands(type=AlterObjectProperty):
            if op.property == attr_name:
                self.discard(op)
                return

    def adapt_value(self, field, value):
        if value is not None and not isinstance(value, field.type):
            value = field.adapt(value)
        return value

    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):
        pass

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

        with self.new_context(context):
            for op in self.ops:
                op.upgrade(context, format_ver, schema)

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

        with self.new_context(context):
            return self._get_ast(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, astnode, *, context=None, schema):
        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(
            astnode, context=context, schema=schema)

    @classmethod
    def _cmd_tree_from_ast(cls, astnode, context, schema):
        cmd = cls._cmd_from_ast(astnode, context, schema)
        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(cmd)):
                    for subastnode in astnode.commands:
                        subcmd = Command.from_ast(
                            subastnode, context=context, schema=schema)
                        if subcmd is not None:
                            cmd.add(subcmd)
            else:
                for subastnode in astnode.commands:
                    subcmd = Command.from_ast(
                        subastnode, context=context, schema=schema)
                    if subcmd is not None:
                        cmd.add(subcmd)

        return cmd

    @classmethod
    def _cmd_from_ast(cls, astnode, context, schema):
        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, 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(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.º 5
0
class Delta(struct.MixedStruct, metaclass=DeltaMeta):
    CURRENT_FORMAT_VERSION = 14

    id = struct.Field(int)
    parent_id = struct.Field(int, None)
    comment = struct.Field(str, None)
    checksum = struct.Field(int)
    checksum_details = struct.Field(list, list)
    deltas = struct.Field(list, None)
    script = struct.Field(str, None)
    snapshot = struct.Field(object, None)
    formatver = struct.Field(int, None)
    preprocess = struct.Field(str, None)
    postprocess = struct.Field(str, None)

    def __init__(self, **kwargs):
        hash_items = (kwargs['parent_id'], kwargs['checksum'],
                      kwargs['comment'])
        kwargs['id'] = persistent_hash('%s%s%s' % hash_items)
        super().__init__(**kwargs)

    def apply(self, schema):
        if self.snapshot is not None:
            try:
                self.snapshot.apply(schema)
            except Exception as e:
                msg = 'failed to apply delta {:032x}'.format(self.id)
                raise DeltaError(msg, delta=self) from e
        elif self.deltas:
            for d in self.deltas:
                try:
                    d.apply(schema)
                except Exception as e:
                    msg = 'failed to apply delta {:032x}'.format(self.id)
                    raise DeltaError(msg, delta=self) from e

    def call_hook(self, session, stage, hook):
        stage_code = getattr(self, stage, None)

        if stage_code is not None:
            with session.transaction():
                def _call_hook():
                    locals = {}

                    exec(stage_code, {}, locals)

                    try:
                        hook_func = locals[hook]
                    except KeyError as e:
                        msg = f'{stage} code does not define {hook}() callable'
                        raise DeltaHookNotFoundError(msg) from e
                    else:
                        hook_func(session)

                _call_hook()

    def upgrade(self, context, schema):
        for d in self.deltas:
            d.upgrade(context, self.formatver, schema)

        if self.formatver < 14:
            if self.deltas[0].preprocess:
                self.preprocess = \
                    s_expr.ExpressionText(self.deltas[0].preprocess)
            if self.deltas[0].postprocess:
                self.postprocess = \
                    s_expr.ExpressionText(self.deltas[0].postprocess)

        self.formatver = context.new_format_ver
Ejemplo n.º 6
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 = schema.get(self.classname, type=metaclass)
        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:
                            del coll[ref_name]

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

        bases = list(scls.get_bases(schema).objects(schema))
        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 == 'LAST':
                idx = len(bases)
            elif pos == 'FIRST':
                idx = 0
            else:
                idx = index[ref]

            bases[idx:idx] = [schema.get(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:
            bases = [schema.get(metaclass.get_default_base_name())]

        schema = scls.set_field_value(schema, 'bases', bases)
        new_mro = compute_mro(schema, scls)[1:]
        schema = scls.set_field_value(schema, 'mro', new_mro)

        if not self.has_attribute_value('mro'):
            self.set_attribute_value('mro', new_mro)

        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_mro = compute_mro(schema, descendant)[1:]
                schema = descendant.set_field_value(schema, 'mro', new_mro)
                alter = alter_cmd(classname=descendant.get_name(schema))
                alter.add(sd.AlterObjectProperty(
                    property='mro',
                    new_value=new_mro,
                ))
                self.add(alter)

        return schema, scls
Ejemplo n.º 7
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):
        metaclass = self.get_schema_metaclass()
        scls = schema.get(self.classname, type=metaclass)
        self.scls = 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)

        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.º 8
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)
Ejemplo n.º 9
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.lang.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 astnode.as_expr:
            new_value = s_expr.ExpressionText(
                edgeql.generate_source(astnode.value, pretty=False))
        else:
            if isinstance(astnode.value, qlast.BaseConstant):
                new_value = qlcompiler.evaluate_ast_to_python_val(
                    astnode.value, schema=schema)

            elif 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)

            else:
                raise ValueError(
                    f'unexpected value in attribute: {astnode.value!r}')

        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.DropAttributeValue(
                name=qlast.ObjectRef(module='', name=self.property))
            return op

        if new_value_empty and old_value_empty:
            return

        if isinstance(value, s_expr.ExpressionText):
            value = edgeql.parse(str(value))
        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)

        as_expr = isinstance(value, qlast.ExpressionText)
        op = qlast.SetField(
            name=qlast.ObjectRef(module='', name=self.property),
            value=value, as_expr=as_expr)
        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.º 10
0
class Code(BaseCode):
    tokens = struct.Field(TokenList, default=None, coerce=True)
Ejemplo n.º 11
0
class Token(BaseCode):
    val = struct.Field(str)