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
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
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)
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)
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, )
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 '')
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)
class Code(BaseCode): tokens = struct.Field(TokenList, default=None, coerce=True)
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)
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)
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
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}')
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)
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))
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)
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)
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
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}' )
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)
class Token(BaseCode): val = struct.Field(str)
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))
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
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
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
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, )
class Code(BaseCode): tokens = struct.Field(checked.CheckedList[Token], default=None, coerce=True)
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