Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
 def get_deps(key: DepGraphKey) -> DepGraphEntry:
     try:
         item = depgraph[key]
     except KeyError:
         item = depgraph[key] = DepGraphEntry(
             item=(),
             deps=ordered.OrderedSet(),
             weak_deps=ordered.OrderedSet(),
         )
     return item
Ejemplo n.º 3
0
def collect_grouping_atoms(
        els: List[qlast.GroupingElement]) -> AbstractSet[str]:
    atoms: ordered.OrderedSet[str] = ordered.OrderedSet()

    def _collect_atom(el: qlast.GroupingAtom) -> None:
        if isinstance(el, qlast.GroupingIdentList):
            for at in el.elements:
                _collect_atom(at)

        else:
            assert isinstance(el, qlast.ObjectRef)
            atoms.add(el.name)

    def _collect_el(el: qlast.GroupingElement) -> None:
        if isinstance(el, qlast.GroupingSets):
            for sub in el.sets:
                _collect_el(sub)
        elif isinstance(el, qlast.GroupingOperation):
            for at in el.elements:
                _collect_atom(at)
        elif isinstance(el, qlast.GroupingSimple):
            _collect_atom(el.element)
        else:
            raise AssertionError('Unknown GroupingElement')

    for el in els:
        _collect_el(el)

    return atoms
Ejemplo n.º 4
0
    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))
Ejemplo n.º 5
0
    def iter_columns(self, writable_only=False, only_self=False):
        cols = collections.OrderedDict()
        cols.update((c.name, c) for c in self._columns
                    if not writable_only or not c.readonly)

        if not only_self:
            for c in reversed(self.bases):
                cols.update((name, bc) for name, bc in c.columns.items()
                            if not writable_only or not bc.readonly)

        return ordered.OrderedSet(cols.values())
Ejemplo n.º 6
0
def sort_objects(schema, objects):
    from . import inheriting

    g = {}

    for obj in sorted(objects, key=lambda o: o.get_name(schema)):
        g[obj.get_name(schema)] = {
            'item': obj, 'merge': [], 'deps': []
        }

        if isinstance(obj, inheriting.InheritingObject):
            obj_bases = obj.get_bases(schema)
            derived_from = obj.get_derived_from(schema)
        else:
            obj_bases = None
            derived_from = None

        deps = g[obj.get_name(schema)]['deps']

        if obj_bases:
            deps.extend(
                b.get_name(schema)
                for b in obj_bases.objects(schema))

            for base in obj_bases.objects(schema):
                base_name = base.get_name(schema)
                if base_name.module != obj.get_name(schema).module:
                    g[base_name] = {'item': base, 'merge': [], 'deps': []}

        if derived_from is not None:
            deps.append(derived_from.get_name(schema))

    if not g:
        return ordered.OrderedSet()

    item = next(iter(g.values()))['item']
    modname = item.get_name(schema).module
    objs = topological.sort(g)
    return ordered.OrderedSet(filter(
        lambda obj: getattr(obj.get_name(schema), 'module', None) ==
        modname, objs))
Ejemplo n.º 7
0
    def sort_expression_fields(self, schema):
        """Transform the subcommands to reduce changes of cycles."""
        def _key(c):
            if isinstance(c, CreateObject):
                return 0
            else:
                return 2

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

        context = CommandContext()
        with context(DeltaRootContext(self)):
            for op in sorted(self.ops, key=_key):
                op.extract_ref_ops(schema, context)
Ejemplo n.º 8
0
    def __init__(self,
                 name,
                 table_name,
                 unique=True,
                 expr=None,
                 predicate=None,
                 inherit=False,
                 metadata=None,
                 columns=None):
        super().__init__(inherit=inherit, metadata=metadata)

        assert table_name[1] != 'feature'

        self.name = name
        self.table_name = table_name
        self._columns = ordered.OrderedSet()
        if columns:
            self.add_columns(columns)
        self.predicate = predicate
        self.unique = unique
        self.expr = expr

        if self.name_in_catalog != self.name:
            self.add_metadata('fullname', self.name)
Ejemplo n.º 9
0
 def __init__(self, name, columns=()):
     super().__init__(name)
     self._columns = ordered.OrderedSet(columns)
Ejemplo n.º 10
0
 def __init__(self, name, *, columns=None, bases=None, constraints=None):
     self.constraints = ordered.OrderedSet(constraints or [])
     self.bases = ordered.OrderedSet(bases or [])
     self.data = []
     super().__init__(name, columns=columns)
Ejemplo n.º 11
0
 def __init__(self, **kwargs):
     super().__init__(**kwargs)
     self.ops = ordered.OrderedSet()
     self.before_ops = ordered.OrderedSet()
Ejemplo n.º 12
0
def _trace_op(
    op: sd.Command,
    opstack: List[sd.Command],
    depgraph: Dict[Tuple[str, str], Dict[str, Any]],
    renames: Dict[str, str],
    renames_r: Dict[str, str],
    strongrefs: Dict[str, str],
    old_schema: Optional[s_schema.Schema],
    new_schema: s_schema.Schema,
) -> None:
    deps: ordered.OrderedSet[Tuple[str, str]] = ordered.OrderedSet()
    graph_key: str
    implicit_ancestors: List[str] = []

    if isinstance(op, sd.CreateObject):
        tag = 'create'
    elif isinstance(op, sd.AlterObject):
        tag = 'alter'
    elif isinstance(op, sd.RenameObject):
        tag = 'rename'
    elif isinstance(op, inheriting.RebaseInheritingObject):
        tag = 'rebase'
    elif isinstance(op, sd.DeleteObject):
        tag = 'delete'
    elif isinstance(op, referencing.AlterOwned):
        tag = 'alterowned'
    elif isinstance(op, sd.AlterObjectProperty):
        tag = 'field'
    else:
        raise RuntimeError(
            f'unexpected delta command type at top level: {op!r}')

    if isinstance(op, (sd.DeleteObject, referencing.AlterOwned)):
        assert old_schema is not None
        obj = get_object(old_schema, op)
        refs = _get_referrers(old_schema, obj, strongrefs)
        for ref in refs:
            ref_name = ref.get_name(old_schema)
            if ((isinstance(obj, referencing.ReferencedObject)
                 and obj.get_referrer(old_schema) == ref)):
                # If the referrer is enclosing the object
                # (i.e. the reference is a refdict reference),
                # we sort the referrer operation first.
                try:
                    ref_item = depgraph[('delete', ref_name)]
                except KeyError:
                    ref_item = depgraph[('delete', ref_name)] = {'deps': set()}

                ref_item['deps'].add((tag, op.classname))

            elif (isinstance(ref, referencing.ReferencedInheritingObject)
                  and (op.classname in {
                      b.get_name(old_schema)
                      for b in ref.get_implicit_ancestors(old_schema)
                  }) and (not isinstance(ref, s_pointers.Pointer)
                          or not ref.get_is_from_alias(old_schema))):
                # If the ref is an implicit descendant (i.e. an inherited ref),
                # we also sort it _after_ the parent, because we'll pull
                # it as a child of the parent op at the time of tree
                # reassembly.
                try:
                    ref_item = depgraph[('delete', ref_name)]
                except KeyError:
                    ref_item = depgraph[('delete', ref_name)] = {'deps': set()}

                ref_item['deps'].add((tag, op.classname))

            elif (isinstance(ref, referencing.ReferencedObject)
                  and ref.get_referrer(old_schema) == obj):
                # Skip refdict.backref_attr to avoid dependency cycles.
                continue

            else:
                # Otherwise, things must be deleted _after_ their referrers
                # have been deleted or altered.
                deps.add(('delete', ref.get_name(old_schema)))

        if isinstance(obj, referencing.ReferencedObject):
            referrer = obj.get_referrer(old_schema)
            if referrer is not None:
                assert isinstance(referrer, so.QualifiedObject)
                referrer_name: str = referrer.get_name(old_schema)
                if referrer_name in renames_r:
                    referrer_name = renames_r[referrer_name]
                deps.add(('rebase', referrer_name))

                if (isinstance(obj, referencing.ReferencedInheritingObject)
                        and (not isinstance(obj, s_pointers.Pointer)
                             or not obj.get_is_from_alias(old_schema))):
                    for ancestor in obj.get_implicit_ancestors(old_schema):
                        ancestor_name = ancestor.get_name(old_schema)
                        implicit_ancestors.append(ancestor_name)

                        try:
                            anc_item = depgraph[('delete', ancestor_name)]
                        except KeyError:
                            anc_item = {'deps': set()}
                            depgraph[('delete', ancestor_name)] = anc_item

                        anc_item['deps'].add((
                            'alterowned',
                            op.classname,
                        ))

        graph_key = op.classname

    elif isinstance(op, sd.AlterObjectProperty):
        if isinstance(op.new_value, so.Object):
            new_value_name = op.new_value.get_name(new_schema)
            deps.add(('create', new_value_name))
            deps.add(('alter', new_value_name))
        elif isinstance(op.new_value, so.ObjectShell):
            nvn = op.new_value.get_name(new_schema)
            if nvn is not None:
                deps.add(('create', nvn))
                deps.add(('alter', nvn))

        parent_op = opstack[-2]
        assert isinstance(parent_op, sd.ObjectCommand)
        graph_key = f'{parent_op.classname}%%{op.property}'
        deps.add(('create', parent_op.classname))

        if isinstance(op.old_value, (so.Object, so.ObjectShell)):
            assert old_schema is not None
            ovn = op.old_value.get_name(old_schema)
            nvn = op.new_value.get_name(new_schema)
            if ovn != nvn:
                try:
                    ov_item = depgraph[('delete', ovn)]
                except KeyError:
                    ov_item = depgraph[('delete', ovn)] = {'deps': set()}

                ov_item['deps'].add((tag, graph_key))

    elif isinstance(op, sd.ObjectCommand):
        # If the object was renamed, use the new name, else use regular.
        name = renames.get(op.classname, op.classname)
        obj = get_object(new_schema, op, name)

        refs = _get_referrers(new_schema, obj, strongrefs)
        for ref in refs:
            ref_name = ref.get_name(new_schema)
            if ref_name in renames_r:
                ref_name = renames_r[ref_name]

            if ((isinstance(ref, referencing.ReferencedObject)
                 and ref.get_referrer(new_schema) == obj)
                    or (isinstance(obj, referencing.ReferencedObject)
                        and obj.get_referrer(new_schema) == ref)):
                # Ignore refs generated by refdict backref.
                continue

            try:
                item = depgraph[('create', ref_name)]
            except KeyError:
                item = depgraph[('create', ref_name)] = {
                    'deps': set(),
                }

            item['deps'].add(('create', op.classname))
            item['deps'].add(('alter', op.classname))
            item['deps'].add(('rename', op.classname))

            try:
                item = depgraph[('alter', ref_name)]
            except KeyError:
                item = depgraph[('alter', ref_name)] = {
                    'deps': set(),
                }

            item['deps'].add(('create', op.classname))
            item['deps'].add(('alter', op.classname))
            item['deps'].add(('rename', op.classname))

            try:
                item = depgraph[('rebase', ref_name)]
            except KeyError:
                item = depgraph[('rebase', ref_name)] = {
                    'deps': set(),
                }

            item['deps'].add(('create', op.classname))
            item['deps'].add(('alter', op.classname))

            try:
                item = depgraph[('rename', ref_name)]
            except KeyError:
                item = depgraph[('rename', ref_name)] = {
                    'deps': set(),
                }

            item['deps'].add(('create', op.classname))
            item['deps'].add(('alter', op.classname))

        if tag in ('create', 'alter'):
            # In a delete/create cycle, deletion must obviously
            # happen first.
            deps.add(('delete', op.classname))

            if isinstance(obj, s_func.Function) and old_schema is not None:
                old_funcs = old_schema.get_functions(
                    sn.shortname_from_fullname(op.classname),
                    default=(),
                )
                for old_func in old_funcs:
                    deps.add(('delete', old_func.get_name(old_schema)))

        if tag == 'alter':
            # Alteration must happen after creation, if any.
            deps.add(('create', op.classname))
            deps.add(('rename', op.classname))
            deps.add(('rebase', op.classname))

        if isinstance(obj, referencing.ReferencedObject):
            referrer = obj.get_referrer(new_schema)
            if referrer is not None:
                assert isinstance(referrer, so.QualifiedObject)
                referrer_name = referrer.get_name(new_schema)
                if referrer_name in renames_r:
                    referrer_name = renames_r[referrer_name]
                deps.add(('create', referrer_name))
                deps.add(('rebase', referrer_name))

                if isinstance(obj, referencing.ReferencedInheritingObject):
                    implicit_ancestors = [
                        b.get_name(new_schema)
                        for b in obj.get_implicit_ancestors(new_schema)
                    ]

                    if not isinstance(op, sd.CreateObject):
                        assert old_schema is not None
                        old_obj = get_object(old_schema, op)
                        assert isinstance(
                            old_obj,
                            referencing.ReferencedInheritingObject,
                        )
                        implicit_ancestors += [
                            b.get_name(old_schema)
                            for b in old_obj.get_implicit_ancestors(old_schema)
                        ]

        graph_key = op.classname

    else:
        raise AssertionError(f'unexpected op type: {op!r}')

    try:
        item = depgraph[(tag, graph_key)]
    except KeyError:
        item = depgraph[(tag, graph_key)] = {'deps': set()}

    item['item'] = opstack
    item['op'] = op
    item['tag'] = tag
    item['deps'].update(deps)
    item['implicit_ancestors'] = implicit_ancestors
Ejemplo n.º 13
0
 def __init__(self, name, columns=None):
     super().__init__()
     self.name = name
     self._columns = ordered.OrderedSet()
     self.add_columns(columns or [])
Ejemplo n.º 14
0
def _trace_op(
    op: sd.Command,
    opbranch: List[sd.Command],
    depgraph: DepGraph,
    renames: Dict[sn.Name, sn.Name],
    renames_r: Dict[sn.Name, sn.Name],
    strongrefs: Dict[sn.Name, sn.Name],
    old_schema: Optional[s_schema.Schema],
    new_schema: s_schema.Schema,
) -> None:
    def get_deps(key: DepGraphKey) -> DepGraphEntry:
        try:
            item = depgraph[key]
        except KeyError:
            item = depgraph[key] = DepGraphEntry(
                item=(),
                deps=ordered.OrderedSet(),
                weak_deps=ordered.OrderedSet(),
            )
        return item

    def record_field_deps(
        op: sd.AlterObjectProperty,
        parent_op: sd.ObjectCommand[so.Object],
    ) -> str:
        if isinstance(op.new_value, (so.Object, so.ObjectShell)):
            obj = op.new_value
            nvn = obj.get_name(new_schema)
            if nvn is not None:
                deps.add(('create', str(nvn)))
                deps.add(('alter', str(nvn)))
                if nvn in renames_r:
                    deps.add(('rename', str(renames_r[nvn])))

            if isinstance(obj, so.ObjectShell):
                obj = obj.resolve(new_schema)
            # For SET TYPE, we want to finish any rebasing into the
            # target type before we change the type.
            if isinstance(obj, so.InheritingObject):
                for desc in obj.descendants(new_schema):
                    deps.add(('rebase', str(desc.get_name(new_schema))))

        graph_key = f'{parent_op.classname}%%{op.property}'
        deps.add(('create', str(parent_op.classname)))
        deps.add(('alter', str(parent_op.classname)))

        if isinstance(op.old_value, (so.Object, so.ObjectShell)):
            assert old_schema is not None
            ovn = op.old_value.get_name(old_schema)
            nvn = op.new_value.get_name(new_schema)
            if ovn != nvn:
                ov_item = get_deps(('delete', str(ovn)))
                ov_item.deps.add((tag, graph_key))

        return graph_key

    def write_dep_matrix(
        dependent: str,
        dependent_tags: Tuple[str, ...],
        dependency: str,
        dependency_tags: Tuple[str, ...],
        *,
        as_weak: bool = False,
    ) -> None:
        for dependent_tag in dependent_tags:
            item = get_deps((dependent_tag, dependent))
            for dependency_tag in dependency_tags:
                if as_weak:
                    item.weak_deps.add((dependency_tag, dependency))
                else:
                    item.deps.add((dependency_tag, dependency))

    def write_ref_deps(
        ref: so.Object,
        obj: so.Object,
        this_name_str: str,
    ) -> None:
        ref_name = ref.get_name(new_schema)
        if ref_name in renames_r:
            ref_name = renames_r[ref_name]
        ref_name_str = str(ref_name)

        if ((isinstance(ref, referencing.ReferencedObject)
             and ref.get_referrer(new_schema) == obj)
                or (isinstance(obj, referencing.ReferencedObject)
                    and obj.get_referrer(new_schema) == ref)):
            # Mostly ignore refs generated by refdict backref, but
            # make create/alter depend on renames of the backref.
            # This makes sure that a rename is done before the innards are
            # modified. DDL doesn't actually require this but some of the
            # internals for producing the DDL do (since otherwise we can
            # generate references to the renamed type in our delta before
            # it is renamed).
            if tag in ('create', 'alter'):
                deps.add(('rename', ref_name_str))

            return

        write_dep_matrix(
            dependent=ref_name_str,
            dependent_tags=('create', 'alter', 'rebase'),
            dependency=this_name_str,
            dependency_tags=('create', 'alter', 'rename'),
        )

        item = get_deps(('rename', ref_name_str))
        item.deps.add(('create', this_name_str))
        item.deps.add(('alter', this_name_str))
        item.deps.add(('rename', this_name_str))

        if isinstance(ref, s_pointers.Pointer):
            # The current item is a type referred to by
            # a link or property in another type.  Set the referring
            # type and its descendants as weak dependents of the current
            # item to reduce the number of unnecessary ALTERs in the
            # final delta, especially ones that might result in SET TYPE
            # commands being generated.
            ref_src = ref.get_source(new_schema)
            if isinstance(ref_src, s_pointers.Pointer):
                ref_src_src = ref_src.get_source(new_schema)
                if ref_src_src is not None:
                    ref_src = ref_src_src
            if ref_src is not None:
                for desc in ref_src.descendants(new_schema) | {ref_src}:
                    desc_name = str(desc.get_name(new_schema))

                    write_dep_matrix(
                        dependent=desc_name,
                        dependent_tags=('create', 'alter'),
                        dependency=this_name_str,
                        dependency_tags=('create', 'alter', 'rename'),
                        as_weak=True,
                    )

    deps: ordered.OrderedSet[Tuple[str, str]] = ordered.OrderedSet()
    graph_key: str
    implicit_ancestors: List[sn.Name] = []

    if isinstance(op, sd.CreateObject):
        tag = 'create'
    elif isinstance(op, sd.AlterObject):
        tag = 'alter'
    elif isinstance(op, sd.RenameObject):
        tag = 'rename'
    elif isinstance(op, inheriting.RebaseInheritingObject):
        tag = 'rebase'
    elif isinstance(op, sd.DeleteObject):
        tag = 'delete'
    elif isinstance(op, referencing.AlterOwned):
        if op.get_attribute_value('owned'):
            tag = 'setowned'
        else:
            tag = 'dropowned'
    elif isinstance(op, (sd.AlterObjectProperty, sd.AlterSpecialObjectField)):
        tag = 'field'
    else:
        raise RuntimeError(
            f'unexpected delta command type at top level: {op!r}')

    if isinstance(op, (sd.DeleteObject, referencing.AlterOwned)):
        assert old_schema is not None
        try:
            obj = get_object(old_schema, op)
        except errors.InvalidReferenceError:
            if isinstance(op, sd.DeleteObject) and op.if_exists:
                # If this is conditional deletion and the object isn't there,
                # then don't bother with analysis, since this command wouldn't
                # get executed.
                return
            else:
                raise
        refs = _get_referrers(old_schema, obj, strongrefs)
        for ref in refs:
            ref_name_str = str(ref.get_name(old_schema))
            if ((isinstance(obj, referencing.ReferencedObject)
                 and obj.get_referrer(old_schema) == ref)):
                # If the referrer is enclosing the object
                # (i.e. the reference is a refdict reference),
                # we sort the enclosed operation first.
                ref_item = get_deps(('delete', ref_name_str))
                ref_item.deps.add((tag, str(op.classname)))

            elif (isinstance(ref, referencing.ReferencedInheritingObject)
                  and (op.classname in {
                      b.get_name(old_schema)
                      for b in ref.get_implicit_ancestors(old_schema)
                  }) and (not isinstance(ref, s_pointers.Pointer)
                          or not ref.get_from_alias(old_schema))):
                # If the ref is an implicit descendant (i.e. an inherited ref),
                # we also sort it _after_ the parent, because we'll pull
                # it as a child of the parent op at the time of tree
                # reassembly.
                ref_item = get_deps(('delete', ref_name_str))
                ref_item.deps.add((tag, str(op.classname)))

            elif (isinstance(ref, referencing.ReferencedObject)
                  and ref.get_referrer(old_schema) == obj):
                # Skip refdict.backref_attr to avoid dependency cycles.
                continue

            else:
                # Otherwise, things must be deleted _after_ their referrers
                # have been deleted or altered.
                deps.add(('delete', ref_name_str))
                # (except for aliases, which in the collection case
                # specifically need the old target deleted before the
                # new one is created)
                if not isinstance(ref, s_expraliases.Alias):
                    deps.add(('alter', ref_name_str))
                if type(ref) == type(obj):
                    deps.add(('rebase', ref_name_str))

                # The deletion of any implicit ancestors needs to come after
                # the deletion of any referrers also.
                if isinstance(obj, referencing.ReferencedInheritingObject):
                    for ancestor in obj.get_implicit_ancestors(old_schema):
                        ancestor_name = ancestor.get_name(old_schema)

                        anc_item = get_deps(('delete', str(ancestor_name)))
                        anc_item.deps.add(('delete', ref_name_str))

        if isinstance(obj, referencing.ReferencedObject):
            referrer = obj.get_referrer(old_schema)
            if referrer is not None:
                assert isinstance(referrer, so.QualifiedObject)
                referrer_name: sn.Name = referrer.get_name(old_schema)
                if referrer_name in renames_r:
                    referrer_name = renames_r[referrer_name]

                # A drop needs to come *before* drop owned on the referrer
                # which will do it itself.
                if tag == 'delete':
                    ref_item = get_deps(('dropowned', str(referrer_name)))
                    ref_item.deps.add(('delete', str(op.classname)))

                # For SET OWNED, we need any rebase of the enclosing
                # object to come *after*, because otherwise obj could
                # get dropped before the SET OWNED takes effect.
                # DROP, also.
                if tag in ('setowned', 'delete'):
                    ref_item = get_deps(('rebase', str(referrer_name)))
                    ref_item.deps.add((tag, str(op.classname)))
                else:
                    deps.add(('rebase', str(referrer_name)))

                if (isinstance(obj, referencing.ReferencedInheritingObject)
                        and (not isinstance(obj, s_pointers.Pointer)
                             or not obj.get_from_alias(old_schema))):
                    for ancestor in obj.get_implicit_ancestors(old_schema):
                        ancestor_name = ancestor.get_name(old_schema)
                        implicit_ancestors.append(ancestor_name)

                        if isinstance(op, referencing.AlterOwned):
                            anc_item = get_deps(('delete', str(ancestor_name)))
                            anc_item.deps.add((tag, str(op.classname)))

                        if tag == 'setowned':
                            # SET OWNED must come before ancestor rebases too
                            anc_item = get_deps(('rebase', str(ancestor_name)))
                            anc_item.deps.add(('setowned', str(op.classname)))

        if tag == 'dropowned':
            deps.add(('alter', str(op.classname)))
        graph_key = str(op.classname)

    elif isinstance(op, sd.AlterObjectProperty):
        parent_op = opbranch[-2]
        assert isinstance(parent_op, sd.ObjectCommand)
        graph_key = record_field_deps(op, parent_op)

    elif isinstance(op, sd.AlterSpecialObjectField):
        parent_op = opbranch[-2]
        assert isinstance(parent_op, sd.ObjectCommand)
        field_op = op._get_attribute_set_cmd(op._field)
        assert field_op is not None
        graph_key = record_field_deps(field_op, parent_op)

    elif isinstance(op, sd.ObjectCommand):
        # If the object was renamed, use the new name, else use regular.
        name = renames.get(op.classname, op.classname)
        obj = get_object(new_schema, op, name)
        this_name_str = str(op.classname)

        if tag == 'rename':
            # On renames, we want to delete any references before we
            # do the rename. This is because for functions and
            # constraints we implicitly rename the object when
            # something it references is renamed, and this implicit
            # rename can interfere with a CREATE/DELETE pair.  So we
            # make sure to put the DELETE before the RENAME of a
            # referenced object. (An improvement would be to elide a
            # CREATE/DELETE pair when it could be implicitly handled
            # by a rename).
            assert old_schema
            old_obj = get_object(old_schema, op, op.classname)
            for ref in _get_referrers(old_schema, old_obj, strongrefs):
                deps.add(('delete', str(ref.get_name(old_schema))))

        refs = _get_referrers(new_schema, obj, strongrefs)
        for ref in refs:
            write_ref_deps(ref, obj, this_name_str)

        if tag in ('create', 'alter'):
            # In a delete/create cycle, deletion must obviously
            # happen first.
            deps.add(('delete', str(op.classname)))
            # Renaming also
            deps.add(('rename', str(op.classname)))

            if isinstance(obj, s_func.Function) and old_schema is not None:
                old_funcs = old_schema.get_functions(
                    sn.shortname_from_fullname(op.classname),
                    default=(),
                )
                for old_func in old_funcs:
                    deps.add(('delete', str(old_func.get_name(old_schema))))

        if tag == 'alter':
            # Alteration must happen after creation, if any.
            deps.add(('create', this_name_str))
            deps.add(('rename', this_name_str))
            deps.add(('rebase', this_name_str))

        if isinstance(obj, referencing.ReferencedObject):
            referrer = obj.get_referrer(new_schema)
            if referrer is not None:
                assert isinstance(referrer, so.QualifiedObject)
                referrer_name = referrer.get_name(new_schema)
                if referrer_name in renames_r:
                    referrer_name = renames_r[referrer_name]
                ref_name_str = str(referrer_name)
                deps.add(('create', ref_name_str))
                if op.ast_ignore_ownership() or tag == 'rename':
                    ref_item = get_deps(('rebase', ref_name_str))
                    ref_item.deps.add((tag, this_name_str))
                else:
                    deps.add(('rebase', ref_name_str))

                # Addition and removal of constraints can cause
                # changes to the cardinality of expressions that refer
                # to them. Add the appropriate dependencies in.
                if (isinstance(obj, s_constraints.Constraint)
                        and isinstance(referrer, s_pointers.Pointer)):
                    refs = _get_referrers(new_schema, referrer, strongrefs)
                    for ref in refs:
                        write_ref_deps(ref, referrer, this_name_str)

                if (isinstance(obj, referencing.ReferencedInheritingObject)
                        # Changes to owned objects can't necessarily be merged
                        # in with parents, so we make sure not to.
                        and not obj.get_owned(new_schema)):
                    implicit_ancestors = [
                        b.get_name(new_schema)
                        for b in obj.get_implicit_ancestors(new_schema)
                    ]

                    if not isinstance(op, sd.CreateObject):
                        assert old_schema is not None
                        name = renames_r.get(op.classname, op.classname)
                        old_obj = get_object(old_schema, op, name)
                        assert isinstance(
                            old_obj,
                            referencing.ReferencedInheritingObject,
                        )
                        implicit_ancestors += [
                            b.get_name(old_schema)
                            for b in old_obj.get_implicit_ancestors(old_schema)
                        ]

        graph_key = this_name_str

    else:
        raise AssertionError(f'unexpected op type: {op!r}')

    item = get_deps((tag, graph_key))

    item.item = tuple(opbranch)
    item.deps |= deps
    item.extra = DepGraphEntryExtra(
        implicit_ancestors=[renames_r.get(a, a) for a in implicit_ancestors], )
Ejemplo n.º 15
0
def _fixup_materialized_sets(ir: irast.Base, *,
                             ctx: context.ContextLevel) -> List[irast.Set]:
    # Make sure that all materialized sets have their views compiled
    flt = lambda n: isinstance(n, irast.Stmt)
    children: List[irast.Stmt] = ast_visitor.find_children(ir, flt)
    for nobe in ctx.source_map.values():
        if nobe.irexpr:
            children += ast_visitor.find_children(nobe.irexpr, flt)

    to_clear = []
    for stmt in ordered.OrderedSet(children):
        if not stmt.materialized_sets:
            continue
        for key in list(stmt.materialized_sets):
            mat_set = stmt.materialized_sets[key]
            assert not mat_set.finalized

            if len(mat_set.uses) <= 1:
                del stmt.materialized_sets[key]
                continue

            ir_set = mat_set.materialized
            assert ir_set.path_scope_id is not None
            new_scope = ctx.env.scope_tree_nodes[ir_set.path_scope_id]
            assert new_scope.parent
            parent = new_scope.parent

            good_reason = False
            for x in mat_set.reason:
                if isinstance(x, irast.MaterializeVolatile):
                    good_reason = True
                elif isinstance(x, irast.MaterializeVisible):
                    # If any of the bindings that the set uses are
                    # *visible* at the definition point and *not
                    # visible* from at least one use point, we need to
                    # materialize, to make sure that the use site sees
                    # the same value for the binding as the definition
                    # point. If it's not visible, then it's just being
                    # used internally and we don't need any special
                    # work.
                    use_scopes = [
                        ctx.env.scope_tree_nodes.get(x.path_scope_id)
                        if x.path_scope_id is not None else None
                        for x in mat_set.use_sets
                    ]
                    for b, _ in x.sets:
                        if parent.is_visible(b, allow_group=True) and not all(
                                use_scope and use_scope.parent and use_scope.
                                parent.is_visible(b, allow_group=True)
                                for use_scope in use_scopes):
                            good_reason = True
                            break

            if not good_reason:
                del stmt.materialized_sets[key]
                continue

            # Compile the view shapes in the set
            with ctx.new() as subctx:
                subctx.implicit_tid_in_shapes = False
                subctx.implicit_tname_in_shapes = False
                subctx.path_scope = new_scope
                viewgen.late_compile_view_shapes(ir_set, ctx=subctx)

            for use_set in mat_set.use_sets:
                if use_set != mat_set.materialized:
                    use_set.is_materialized_ref = True
                    # XXX: Deleting it on linkprops breaks a bunch of
                    # linkprop related DML...
                    if not use_set.path_id.is_linkprop_path():
                        to_clear.append(use_set)

            assert (not any(use.src_path() for use in mat_set.uses)
                    or mat_set.materialized.rptr
                    ), f"materialized ptr {mat_set.uses} missing rptr"
            mat_set.finalized = True

    return to_clear