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