Ejemplo n.º 1
0
def linearize_delta(
    delta: sd.DeltaRoot,
    old_schema: Optional[s_schema.Schema],
    new_schema: s_schema.Schema,
) -> sd.DeltaRoot:
    """Reorder the *delta* tree in-place to satisfy command dependency order.

    Args:
        delta:
            Input delta command tree.
        old_schema:
            Schema used to resolve original object state.
        new_schema:
            Schema used to resolve final schema state.

    Returns:
        Input delta tree reordered according to topological ordering of
        commands.
    """

    # We take the scatter-sort-gather approach here, where the original
    # tree is broken up into linear branches, which are then sorted
    # and reassembled back into a tree.

    opmap: Dict[sd.Command, List[sd.Command]] = {}
    strongrefs: Dict[str, str] = {}

    for op in _get_sorted_subcommands(delta):
        _break_down(opmap, strongrefs, [delta, op])

    depgraph: Dict[Tuple[str, str], Dict[str, Any]] = {}
    renames: Dict[str, str] = {}
    renames_r: Dict[str, str] = {}
    deletions: Set[str] = set()

    for op in opmap:
        if isinstance(op, sd.RenameObject):
            renames[op.classname] = op.new_name
            renames_r[op.new_name] = op.classname
        elif isinstance(op, sd.DeleteObject):
            deletions.add(op.classname)

    for op, opbranch in opmap.items():
        if isinstance(op, sd.AlterObject) and not op.get_subcommands():
            continue

        _trace_op(op, opbranch, depgraph, renames, renames_r, strongrefs,
                  old_schema, new_schema)

    depgraph = dict(
        filter(lambda i: i[1].get('item') is not None, depgraph.items()))

    everything = set(depgraph)
    for item in depgraph.values():
        item['deps'] = item['deps'] & everything

    sortedlist = [i[1] for i in topological.sort(depgraph, return_record=True)]
    reconstructed = reconstruct_tree(sortedlist, depgraph)
    delta.replace_all(reconstructed.get_subcommands())
    return delta
Ejemplo n.º 2
0
    async def order_objtypes(self, schema, exprmap):
        g = {}
        for objtype in schema.get_objects(type=s_objtypes.BaseObjectType):
            g[objtype.get_name(schema)] = {
                "item": objtype,
                "merge": [],
                "deps": []
            }
            objtype_bases = objtype.get_bases(schema).objects(schema)
            if objtype_bases:
                g[objtype.get_name(schema)]["merge"].extend(
                    b.get_name(schema) for b in objtype_bases)

            try:
                expr = exprmap[objtype.get_name(schema)]
            except KeyError:
                pass
            else:
                if expr is not None:
                    schema = objtype.set_field_value(
                        schema, 'expr', self.unpack_expr(expr, schema))

        objtypes = topological.sort(g)
        for objtype in objtypes:
            schema = objtype.finalize(schema)

        return schema
Ejemplo n.º 3
0
def sdl_to_ddl(schema, documents):
    ddlgraph = {}
    mods = []

    ctx = LayoutTraceContext(
        schema,
        local_modules=frozenset(mod for mod, schema_decl in documents.items()),
    )

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            if isinstance(decl_ast, qlast.CreateObject):
                _, fq_name = ctx.get_fq_name(decl_ast)
                if isinstance(decl_ast,
                              (qlast.CreateObjectType, qlast.CreateAlias)):
                    ctx.objects[fq_name] = qltracer.ObjectType(fq_name)

                elif isinstance(decl_ast, qlast.CreateScalarType):
                    ctx.objects[fq_name] = qltracer.Type(fq_name)

                elif isinstance(decl_ast,
                                (qlast.CreateLink, qlast.CreateProperty)):
                    ctx.objects[fq_name] = qltracer.Pointer(fq_name,
                                                            source=None,
                                                            target=None)
                elif isinstance(decl_ast, qlast.CreateFunction):
                    ctx.objects[fq_name] = qltracer.Function(fq_name)
                elif isinstance(decl_ast, qlast.CreateConstraint):
                    ctx.objects[fq_name] = qltracer.Constraint(fq_name)
                elif isinstance(decl_ast, qlast.CreateAnnotation):
                    ctx.objects[fq_name] = qltracer.Annotation(fq_name)
                else:
                    raise AssertionError(
                        f'unexpected SDL declaration: {decl_ast}')

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            trace_layout(decl_ast, ctx=ctx)

    # compute the ancestors graph
    for fq_name in ctx.parents.keys():
        ctx.ancestors[fq_name] = get_ancestors(fq_name, ctx.ancestors,
                                               ctx.parents)

    topological.normalize(ctx.inh_graph, _merge_items)

    ctx = DepTraceContext(schema, ddlgraph, ctx.objects, ctx.parents,
                          ctx.ancestors, ctx.defdeps, ctx.constraints)
    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        # module needs to be created regardless of whether its
        # contents are empty or not
        mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name)))
        for decl_ast in declarations:
            trace_dependencies(decl_ast, ctx=ctx)

    return mods + list(topological.sort(ddlgraph, allow_unresolved=False))
Ejemplo n.º 4
0
    async def order_link_properties(self, schema):
        g = {}

        for prop in schema.get_objects(type=s_props.Property):
            g[prop.get_name(schema)] = {"item": prop, "merge": [], "deps": []}
            prop_bases = prop.get_bases(schema).objects(schema)
            if prop_bases:
                g[prop.get_name(schema)]['merge'].extend(
                    b.get_name(schema) for b in prop_bases)

        props = topological.sort(g)

        for prop in props:
            schema = prop.finalize(schema)

        return schema
Ejemplo n.º 5
0
    async def order_links(self, schema):
        g = {}

        for link in schema.get_objects(type=s_links.Link):
            g[link.get_name(schema)] = {"item": link, "merge": [], "deps": []}
            link_bases = link.get_bases(schema).objects(schema)
            if link_bases:
                g[link.get_name(schema)]['merge'].extend(
                    b.get_name(schema) for b in link_bases)

        links = topological.sort(g)

        for link in links:
            schema = link.finalize(schema)

        return schema
Ejemplo n.º 6
0
    async def order_objtypes(self, schema):
        g = {}
        for objtype in schema.get_objects(type=s_objtypes.BaseObjectType):
            g[objtype.get_name(schema)] = {
                "item": objtype, "merge": [], "deps": []
            }
            objtype_bases = objtype.get_bases(schema).objects(schema)
            if objtype_bases:
                g[objtype.get_name(schema)]["merge"].extend(
                    b.get_name(schema) for b in objtype_bases)

        objtypes = topological.sort(g)
        for objtype in objtypes:
            schema = objtype.finalize(schema)

        return schema
Ejemplo n.º 7
0
async def _get_dbs_and_roles(
    pgconn: asyncpg.Connection, ) -> Tuple[List[str], List[str]]:
    compiler = edbcompiler.Compiler()
    await compiler.initialize_from_pg(pgconn)
    compilerctx = edbcompiler.new_compiler_context(
        user_schema=s_schema.FlatSchema(),
        global_schema=s_schema.FlatSchema(),
        expected_cardinality_one=False,
        single_statement=True,
        output_format=edbcompiler.IoFormat.JSON,
        bootstrap_mode=True,
    )

    _, get_databases_sql = edbcompiler.compile_edgeql_script(
        compiler,
        compilerctx,
        'SELECT sys::Database.name',
    )

    databases = list(
        sorted(
            json.loads(await pgconn.fetchval(get_databases_sql)),
            key=lambda dname: edbdef.EDGEDB_TEMPLATE_DB in dname,
        ))

    _, get_roles_sql = edbcompiler.compile_edgeql_script(
        compiler,
        compilerctx,
        '''SELECT sys::Role {
            name,
            parents := .member_of.name,
        }''',
    )

    roles = json.loads(await pgconn.fetchval(get_roles_sql))
    sorted_roles = list(
        topological.sort({
            r['name']: topological.DepGraphEntry(
                item=r['name'],
                deps=r['parents'],
                extra=False,
            )
            for r in roles
        }))

    return databases, sorted_roles
Ejemplo n.º 8
0
async def _get_dbs_and_roles(pgconn) -> Tuple[List[str], List[str]]:
    compiler = edbcompiler.Compiler({})
    await compiler.ensure_initialized(pgconn)
    schema = compiler.get_std_schema()
    compilerctx = edbcompiler.new_compiler_context(
        schema,
        expected_cardinality_one=False,
        single_statement=True,
        output_format=edbcompiler.IoFormat.JSON,
    )

    schema, get_databases_sql = edbcompiler.compile_edgeql_script(
        compiler,
        compilerctx,
        'SELECT sys::Database.name',
    )

    databases = list(
        sorted(
            json.loads(await pgconn.fetchval(get_databases_sql)),
            key=lambda dname: dname == edbdef.EDGEDB_TEMPLATE_DB,
        ))

    schema, get_roles_sql = edbcompiler.compile_edgeql_script(
        compiler,
        compilerctx,
        '''SELECT sys::Role {
            name,
            parents := .member_of.name,
        }''',
    )

    roles = json.loads(await pgconn.fetchval(get_roles_sql))
    sorted_roles = list(
        topological.sort({
            r['name']: topological.DepGraphEntry(
                item=r['name'],
                deps=r['parents'],
                extra=False,
            )
            for r in roles
        }))

    return databases, sorted_roles
Ejemplo n.º 9
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.º 10
0
def sdl_to_ddl(schema, declarations):
    ddlgraph = {}
    mods = []

    ctx = LayoutTraceContext(
        schema,
        local_modules=frozenset(mod for mod, schema_decl in declarations),
    )

    for module_name, schema_ast in declarations:
        for decl_ast in schema_ast.declarations:
            if isinstance(decl_ast, qlast.CreateObject):
                fq_name = f'{module_name}::{decl_ast.name.name}'
                if isinstance(decl_ast,
                              (qlast.CreateObjectType, qlast.CreateView)):
                    ctx.objects[fq_name] = qltracer.ObjectType(fq_name)

                elif isinstance(decl_ast, qlast.CreateScalarType):
                    ctx.objects[fq_name] = qltracer.Type(fq_name)

                elif isinstance(decl_ast,
                                (qlast.CreateLink, qlast.CreateProperty)):
                    ctx.objects[fq_name] = qltracer.Pointer(fq_name,
                                                            source=None,
                                                            target=None)

    for module_name, decl_ast in declarations:
        ctx.set_module(module_name)
        trace_layout(decl_ast, ctx=ctx)

    topological.normalize(ctx.inh_graph, _merge_items)

    ctx = DepTraceContext(schema, ddlgraph, ctx.objects)
    for module_name, decl_ast in declarations:
        ctx.set_module(module_name)
        trace_dependencies(decl_ast, ctx=ctx)
        mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name)))

    return mods + list(topological.sort(ddlgraph, allow_unresolved=False))
Ejemplo n.º 11
0
async def _get_dbs_and_roles(pgconn) -> Tuple[List[str], List[str]]:
    std_schema = await compiler.load_std_schema(pgconn)

    schema, get_databases_sql = compiler.compile_bootstrap_script(
        std_schema,
        std_schema,
        'SELECT sys::Database.name',
        expected_cardinality_one=False,
        single_statement=True,
    )

    databases = list(
        sorted(
            json.loads(await pgconn.fetchval(get_databases_sql)),
            key=lambda dname: dname == edbdef.EDGEDB_TEMPLATE_DB,
        ))

    schema, get_roles_sql = compiler.compile_bootstrap_script(
        std_schema,
        std_schema,
        '''SELECT sys::Role {
            name,
            parents := .member_of.name,
        }''',
        expected_cardinality_one=False,
        single_statement=True,
    )

    roles = json.loads(await pgconn.fetchval(get_roles_sql))
    sorted_roles = list(
        topological.sort({
            r['name']: {
                'item': r['name'],
                'deps': r['parents'],
            }
            for r in roles
        }))

    return databases, sorted_roles
Ejemplo n.º 12
0
def sdl_to_ddl(
    schema: s_schema.Schema,
    documents: Mapping[str, List[qlast.DDL]],
) -> Tuple[qlast.DDLCommand, ...]:

    ddlgraph: DDLGraph = {}
    mods: List[qlast.DDLCommand] = []

    ctx = LayoutTraceContext(
        schema,
        local_modules=frozenset(mod for mod in documents),
    )

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            if isinstance(decl_ast, qlast.CreateObject):
                _, fq_name = ctx.get_fq_name(decl_ast)

                if isinstance(decl_ast, (qlast.CreateObjectType,
                                         qlast.CreateAlias)):
                    ctx.objects[fq_name] = qltracer.ObjectType(fq_name)

                elif isinstance(decl_ast, qlast.CreateScalarType):
                    ctx.objects[fq_name] = qltracer.Type(fq_name)

                elif isinstance(decl_ast, (qlast.CreateLink,
                                           qlast.CreateProperty)):
                    ctx.objects[fq_name] = qltracer.Pointer(
                        fq_name, source=None, target=None)
                elif isinstance(decl_ast, qlast.CreateFunction):
                    ctx.objects[fq_name] = qltracer.Function(fq_name)
                elif isinstance(decl_ast, qlast.CreateConstraint):
                    ctx.objects[fq_name] = qltracer.Constraint(fq_name)
                elif isinstance(decl_ast, qlast.CreateAnnotation):
                    ctx.objects[fq_name] = qltracer.Annotation(fq_name)
                else:
                    raise AssertionError(
                        f'unexpected SDL declaration: {decl_ast}')

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            trace_layout(decl_ast, ctx=ctx)

    # compute the ancestors graph
    for obj_name in ctx.parents.keys():
        ctx.ancestors[obj_name] = get_ancestors(
            obj_name, ctx.ancestors, ctx.parents)

    topological.normalize(
        ctx.inh_graph,
        merger=_graph_merge_cb,  # type: ignore
        schema=schema,
    )

    tracectx = DepTraceContext(
        schema, ddlgraph, ctx.objects, ctx.parents, ctx.ancestors,
        ctx.defdeps, ctx.constraints
    )
    for module_name, declarations in documents.items():
        tracectx.set_module(module_name)
        # module needs to be created regardless of whether its
        # contents are empty or not
        mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name)))
        for decl_ast in declarations:
            trace_dependencies(decl_ast, ctx=tracectx)

    ordered = topological.sort(ddlgraph, allow_unresolved=False)
    return tuple(mods) + tuple(ordered)
Ejemplo n.º 13
0
def linearize_delta(delta, old_schema, new_schema):
    """Sort delta operations to dependency order."""

    opmap = {}
    strongrefs = {}

    for op in delta.get_subcommands():
        _break_down(opmap, strongrefs, [delta, op])

    depgraph = {}
    renames = {}
    renames_r = {}

    for op in opmap:
        if isinstance(op, sd.RenameObject):
            renames[op.classname] = op.new_name
            renames_r[op.new_name] = op.classname

    for op, opstack in opmap.items():
        _trace_op(op, opstack, depgraph, renames, renames_r, strongrefs,
                  old_schema, new_schema)

    depgraph = dict(
        filter(lambda i: i[1].get('item') is not None, depgraph.items()))

    ordered = list(
        topological.sort(depgraph, allow_unresolved=True, return_record=True))

    parents = {}
    dependencies = collections.defaultdict(set)
    max_offset = len(ordered)
    offsets = {}
    ops = []

    for key, info in ordered:
        op = info['op']
        opstack = opmap[op]
        parent = opstack[1]
        for dep in info['deps']:
            dep_item = depgraph.get(dep)
            if dep_item is None:
                continue
            dep_op = dep_item['op']
            dep_stack = opmap[dep_op]
            dep_parent = dep_stack[1]
            if ((dep_item['tag'], dep_parent.classname) !=
                (info['tag'], parent.classname)):
                dependencies[op].add(dep_op)

    for key, info in ordered:
        op = info['op']
        if (isinstance(op, sd.AlterObject) and not len(op.get_subcommands())):
            continue

        opstack = opmap[op]
        parent = opstack[1]

        reattachment_offset = 1

        for offset, ancestor_op in enumerate(reversed(opstack[1:-1])):
            mcls = ancestor_op.get_schema_metaclass()
            create_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die(
                sd.CreateObject, mcls)
            ancestor_key = (create_cmd_cls, ancestor_op.classname)

            attached_parent = parents.get(ancestor_key)
            if attached_parent is not None:
                parent_offset = offsets[attached_parent]
                deps = dependencies.get(op)

                ok_to_reattach = (not deps or all(
                    offsets.get(dep, max_offset) < parent_offset
                    for dep in deps))

                if ok_to_reattach:
                    reattachment_offset = -(offset + 1)
                    attached_parent.add(opstack[reattachment_offset])
                    offset = parent_offset

                break

            ancestor_key = (type(ancestor_op), ancestor_op.classname)
            attached_parent = parents.get(ancestor_key)
            if attached_parent is not None:
                parent_offset = offsets[attached_parent]
                deps = dependencies.get(op)
                ok_to_reattach = (not deps or all(
                    offsets.get(dep, max_offset) < parent_offset
                    for dep in deps))

                if ok_to_reattach:
                    reattachment_offset = -(offset + 1)
                    attached_parent.add(opstack[reattachment_offset])
                    offset = parent_offset

                break

        if reattachment_offset == 1:
            # Haven't seen this op branch yet
            ops.append(parent)
            offset = len(ops) - 1

        for op in opstack[reattachment_offset:]:
            if not isinstance(op, sd.AlterObjectProperty):
                ancestor_key = (type(op), op.classname)
                parents[ancestor_key] = op

            offsets[op] = offset

    delta.replace(ops)

    return delta
Ejemplo n.º 14
0
def linearize_delta(
    delta: sd.DeltaRoot,
    old_schema: Optional[s_schema.Schema],
    new_schema: s_schema.Schema,
) -> sd.DeltaRoot:
    """Sort delta operations to dependency order."""

    opmap: Dict[sd.Command, List[sd.Command]] = {}
    strongrefs: Dict[str, str] = {}

    for op in _get_sorted_subcommands(delta):
        _break_down(opmap, strongrefs, [delta, op])

    depgraph: Dict[Tuple[str, str], Dict[str, Any]] = {}
    renames: Dict[str, str] = {}
    renames_r: Dict[str, str] = {}
    parents: Dict[sd.Command, sd.Command] = {}
    deletions: Set[str] = set()

    for op in opmap:
        if isinstance(op, sd.RenameObject):
            renames[op.classname] = op.new_name
            renames_r[op.new_name] = op.classname
        elif isinstance(op, sd.DeleteObject):
            deletions.add(op.classname)

    for op, opstack in opmap.items():
        if isinstance(op, sd.AlterObject) and not op.get_subcommands():
            continue

        _trace_op(op, opstack, depgraph, renames, renames_r, strongrefs,
                  old_schema, new_schema)

    depgraph = dict(
        filter(lambda i: i[1].get('item') is not None, depgraph.items()))

    sortedlist = list(
        topological.sort(depgraph, allow_unresolved=True, return_record=True))

    dependencies: Dict[sd.Command, Set[sd.Command]]
    dependencies = collections.defaultdict(set)
    max_offset = len(sortedlist)
    offsets: Dict[sd.Command, int] = {}
    ops: List[sd.Command] = []
    opindex: Dict[Tuple[Type[sd.ObjectCommand[so.Object]], str],
                  sd.ObjectCommand[so.Object]] = {}

    def ok_to_move_to(
        op_to_move: sd.Command,
        op_to_move_to: sd.ObjectCommand[so.Object],
    ) -> bool:
        move_offset = offsets[op_to_move_to]
        deps = {
            dep
            for dep in dependencies.get(op_to_move, set())
            if parents[dep] != op_to_move
        }
        # It's OK to reattach if no dependency has offset
        # higher than the op we're moving to.
        return all(offsets.get(dep, max_offset) <= move_offset for dep in deps)

    for _key, info in sortedlist:
        op = info['op']
        opstack = info['item']
        for i, pop in enumerate(opstack[1:]):
            parents[pop] = opstack[i]
        parent = _get_parent_op(opstack)
        for dep in info['deps']:
            dep_item = depgraph.get(dep)
            if dep_item is None:
                continue
            dep_op = dep_item['op']
            dep_stack = dep_item['item']
            dep_parent = _get_parent_op(dep_stack)
            assert isinstance(dep_parent, sd.ObjectCommand)
            dependencies[op].add(dep_op)

    for _key, info in sortedlist:
        op = info['op']
        # Elide empty ALTER statements from output.
        if isinstance(op, sd.AlterObject) and not op.get_subcommands():
            continue

        opstack = info['item']
        parent = _get_parent_op(opstack)
        reattachment_depth = 1
        for depth, ancestor_op in enumerate(reversed(opstack[1:-1])):
            assert isinstance(ancestor_op, sd.ObjectCommand)
            mcls = ancestor_op.get_schema_metaclass()
            create_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die(
                sd.CreateObject, mcls)
            attached_root: Optional[sd.Command] = None

            # Try attaching to a "Create" op, if that doesn't work
            # attach to whatever the ancestor is right now.
            ancestor_keys: List[Tuple[Type[sd.ObjectCommand[so.Object]],
                                      str]] = []
            if type(ancestor_op) != create_cmd_cls:
                ancestor_keys.append((create_cmd_cls, ancestor_op.classname))
            ancestor_keys.append((type(ancestor_op), ancestor_op.classname))

            for ancestor_key in ancestor_keys:
                # The root operation is the top-level
                # operation in the delta.
                attached_root = opindex.get(ancestor_key)
                if attached_root is not None:
                    if ok_to_move_to(op, attached_root):
                        reattachment_depth = -(depth + 1)
                        attached_root.add(opstack[reattachment_depth])
                        parents[opstack[reattachment_depth]] = attached_root
                        # record where this branch was reattached
                        offset = offsets[attached_root]
                    break

            if attached_root is not None:
                # As long as we found our root operation, we consider
                # the potential reattachment process complete.
                break

        if reattachment_depth == 1:
            # Haven't seen this op branch yet
            ops.append(parent)
            offset = len(ops) - 1

        for op in opstack[reattachment_depth:]:
            if isinstance(op, sd.ObjectCommand):
                ancestor_key = (type(op), op.classname)
                opindex[ancestor_key] = op

            offsets[op] = offset

    delta.replace_all(ops)

    return delta
Ejemplo n.º 15
0
def sdl_to_ddl(
    schema: s_schema.Schema,
    documents: Mapping[str, List[qlast.DDL]],
) -> Tuple[qlast.DDLCommand, ...]:

    ddlgraph: DDLGraph = {}
    mods: List[qlast.DDLCommand] = []

    ctx = LayoutTraceContext(
        schema,
        local_modules=frozenset(mod for mod in documents),
    )

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            if isinstance(decl_ast, qlast.CreateObject):
                _, fq_name = ctx.get_fq_name(decl_ast)

                if isinstance(decl_ast, qlast.CreateObjectType):
                    ctx.objects[fq_name] = qltracer.ObjectType(fq_name)
                elif isinstance(decl_ast, qlast.CreateAlias):
                    ctx.objects[fq_name] = qltracer.Alias(fq_name)
                elif isinstance(decl_ast, qlast.CreateScalarType):
                    ctx.objects[fq_name] = qltracer.ScalarType(fq_name)
                elif isinstance(decl_ast, qlast.CreateLink):
                    ctx.objects[fq_name] = qltracer.Link(fq_name,
                                                         source=None,
                                                         target=None)
                elif isinstance(decl_ast, qlast.CreateProperty):
                    ctx.objects[fq_name] = qltracer.Property(fq_name,
                                                             source=None,
                                                             target=None)
                elif isinstance(decl_ast, qlast.CreateFunction):
                    ctx.objects[fq_name] = qltracer.Function(fq_name)
                elif isinstance(decl_ast, qlast.CreateConstraint):
                    ctx.objects[fq_name] = qltracer.Constraint(fq_name)
                elif isinstance(decl_ast, qlast.CreateAnnotation):
                    ctx.objects[fq_name] = qltracer.Annotation(fq_name)
                else:
                    raise AssertionError(
                        f'unexpected SDL declaration: {decl_ast}')

    for module_name, declarations in documents.items():
        ctx.set_module(module_name)
        for decl_ast in declarations:
            trace_layout(decl_ast, ctx=ctx)

    # compute the ancestors graph
    for obj_name in ctx.parents.keys():
        ctx.ancestors[obj_name] = get_ancestors(obj_name, ctx.ancestors,
                                                ctx.parents)

    topological.normalize(
        ctx.inh_graph,
        merger=_graph_merge_cb,  # type: ignore
        schema=schema,
    )

    tracectx = DepTraceContext(schema, ddlgraph, ctx.objects, ctx.parents,
                               ctx.ancestors, ctx.defdeps, ctx.constraints)
    for module_name, declarations in documents.items():
        tracectx.set_module(module_name)
        # module needs to be created regardless of whether its
        # contents are empty or not
        mods.append(qlast.CreateModule(name=qlast.ObjectRef(name=module_name)))
        for decl_ast in declarations:
            trace_dependencies(decl_ast, ctx=tracectx)

    try:
        ordered = topological.sort(ddlgraph, allow_unresolved=False)
    except topological.CycleError as e:
        assert isinstance(e.item, s_name.QualName)
        node = tracectx.ddlgraph[e.item].item
        item_vn = get_verbosename_from_fqname(e.item, tracectx)

        if e.path is not None and len(e.path):
            # Recursion involving more than one schema object.
            rec_vn = get_verbosename_from_fqname(e.path[-1], tracectx)
            msg = (f'definition dependency cycle between {rec_vn} '
                   f'and {item_vn}')
        else:
            # A single schema object with a recursive definition.
            msg = f'{item_vn} is defined recursively'

        raise errors.InvalidDefinitionError(msg, context=node.context) from e

    return tuple(mods) + tuple(ordered)
Ejemplo n.º 16
0
def linearize_delta(delta: sd.DeltaRoot, old_schema: Optional[s_schema.Schema],
                    new_schema: s_schema.Schema) -> sd.DeltaRoot:
    """Sort delta operations to dependency order."""

    opmap: Dict[sd.Command, List[sd.Command]] = {}
    strongrefs: Dict[str, str] = {}

    for op in delta.get_subcommands():
        _break_down(opmap, strongrefs, [delta, op])

    depgraph: Dict[Tuple[str, str], Dict[str, Any]] = {}
    renames: Dict[str, str] = {}
    renames_r: Dict[str, str] = {}

    for op in opmap:
        if isinstance(op, sd.RenameObject):
            renames[op.classname] = op.new_name
            renames_r[op.new_name] = op.classname

    for op, opstack in opmap.items():
        _trace_op(op, opstack, depgraph, renames, renames_r, strongrefs,
                  old_schema, new_schema)

    depgraph = dict(
        filter(lambda i: i[1].get('item') is not None, depgraph.items()))

    ordered = list(
        topological.sort(depgraph, allow_unresolved=True, return_record=True))

    parents: Dict[Tuple[Type[sd.ObjectCommand], str], sd.ObjectCommand]
    dependencies: Dict[sd.Command, Set[sd.Command]]
    parents = {}
    dependencies = collections.defaultdict(set)
    max_offset = len(ordered)
    offsets: Dict[sd.Command, int] = {}
    ops: List[sd.Command] = []

    for _key, info in ordered:
        op = info['op']
        opstack = opmap[op]
        parent = _get_parent_op(opstack)
        for dep in info['deps']:
            dep_item = depgraph.get(dep)
            if dep_item is None:
                continue
            dep_op = dep_item['op']
            dep_stack = opmap[dep_op]
            dep_parent = _get_parent_op(dep_stack)
            assert isinstance(dep_parent, sd.ObjectCommand)
            dependencies[op].add(dep_op)

    for _key, info in ordered:
        op = info['op']
        # Elide empty ALTER statements from output.
        if (isinstance(op, sd.AlterObject) and not len(op.get_subcommands())):
            continue

        opstack = opmap[op]
        parent = _get_parent_op(opstack)

        reattachment_depth = 1

        for depth, ancestor_op in enumerate(reversed(opstack[1:-1])):
            assert isinstance(ancestor_op, sd.ObjectCommand)
            mcls = ancestor_op.get_schema_metaclass()
            create_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die(
                sd.CreateObject, mcls)
            attached_root: Optional[sd.ObjectCommand] = None

            # Try attaching to a "Create" op, if that doesn't work
            # attach to whatever the ancestor is right now.
            for ancestor_cls in [create_cmd_cls, type(ancestor_op)]:
                ancestor_key: Tuple[Type[sd.ObjectCommand],
                                    str] = (ancestor_cls,
                                            ancestor_op.classname)
                # The root operation is the top-level operation in the delta.
                attached_root = parents.get(ancestor_key)
                if attached_root is not None:
                    root_offset = offsets[attached_root]
                    deps = dependencies.get(op, set())

                    # It's OK to reattach if no dependency has offset
                    # higher than our root operation.
                    if all(
                            offsets.get(dep, max_offset) <= root_offset
                            for dep in deps):
                        reattachment_depth = -(depth + 1)
                        attached_root.add(opstack[reattachment_depth])
                        # record where this branch was reattached
                        offset = root_offset

                    break

            if attached_root is not None:
                # As long as we found our root operation, we consider
                # the potential reattachment process complete.
                break

        if reattachment_depth == 1:
            # Haven't seen this op branch yet
            ops.append(parent)
            offset = len(ops) - 1

        for op in opstack[reattachment_depth:]:
            if isinstance(op, sd.ObjectCommand):
                ancestor_key = (type(op), op.classname)
                parents[ancestor_key] = op

            offsets[op] = offset

    delta.replace(ops)

    return delta