def trace_SetField(node: qlast.SetField, *, ctx: DepTraceContext): deps = set() for dep in qltracer.trace_refs( node.value, schema=ctx.schema, module=ctx.module, objects=ctx.objects, ): if dep.startswith(f"{ctx.module}::"): deps.add(dep) _register_item(node, deps=deps, ctx=ctx)
def trace_SetField(node: qlast.SetField, *, ctx: DepTraceContext): deps = set() for dep in qltracer.trace_refs( node.value, schema=ctx.schema, module=ctx.module, objects=ctx.objects, ): # ignore std module dependencies if not STD_PREFIX_RE.match(dep): deps.add(dep) _register_item(node, deps=deps, ctx=ctx)
def trace_SetField( node: qlast.SetField, *, ctx: DepTraceContext, ) -> None: deps = set() assert node.value, "sdl SetField should always have value" for dep in qltracer.trace_refs( node.value, schema=ctx.schema, module=ctx.module, objects=ctx.objects, params={}, ): # ignore std module dependencies if dep.get_module_name() not in s_schema.STD_MODULES: deps.add(dep) _register_item(node, deps=deps, ctx=ctx)
def _register_item(decl, *, deps=None, hard_dep_exprs=None, loop_control=None, source=None, subject=None, extra_name=None, ctx): if deps: deps = set(deps) else: deps = set() op = orig_op = copy.copy(decl) if isinstance(decl, qlast.CreateConcretePointer): name = decl.name.name else: name = ctx.get_local_name(decl.name) if ctx.depstack: op.alter_if_exists = True top_parent = parent = copy.copy(ctx.depstack[0][0]) parent.commands = [] for entry, _entry_name in ctx.depstack[1:]: entry_op = copy.copy(entry) entry_op.commands = [] parent.commands.append(entry_op) parent = entry_op parent.commands.append(op) op = top_parent fq_name = ctx.depstack[-1][1] + "@" + name else: op.aliases = [qlast.ModuleAliasDecl(alias=None, module=ctx.module)] fq_name = name if extra_name is not None: fq_name = f'{fq_name}:{extra_name}' node = { "item": op, "deps": {n for _, n in ctx.depstack if n != loop_control}, } ctx.ddlgraph[fq_name] = node if hasattr(decl, "bases"): bases = set() for ref in _get_bases(decl, ctx=ctx): if ref.module == ctx.module: bases.add(ref) deps.update(bases) ctx.inhgraph[fq_name] = bases if ctx.depstack: parent_bases = ctx.inhgraph.get(ctx.depstack[-1][1]) if parent_bases: for parent_base in parent_bases: base_item = f'{parent_base}@{name}' if base_item in ctx.objects: deps.add(base_item) ast_subcommands = getattr(decl, 'commands', []) commands = [] if ast_subcommands: subcmds = [] for cmd in ast_subcommands: if isinstance(cmd, qlast.ObjectDDL): subcmds.append(cmd) elif (isinstance(cmd, qlast.SetField) and not isinstance(cmd.value, qlast.BaseConstant) and not isinstance(op, qlast.CreateView)): subcmds.append(cmd) else: commands.append(cmd) if subcmds: alter_name = f"Alter{decl.__class__.__name__[len('Create'):]}" alter_cls = getattr(qlast, alter_name) alter_cmd = alter_cls(name=decl.name) # indexes need to preserve their "on" expression if alter_name == 'AlterIndex': # find the original expr, which will be in non-normalized form for sub in op.commands: if isinstance(sub, qlast.CreateIndex): alter_cmd.expr = sub.expr break # constraints need to preserve their "on" expression elif alter_name == 'AlterConcreteConstraint': # find the original expr, which will be in non-normalized form for sub in op.commands: if isinstance(sub, qlast.CreateConcreteConstraint): alter_cmd.subjectexpr = sub.subjectexpr break if not ctx.depstack: alter_cmd.aliases = [ qlast.ModuleAliasDecl(alias=None, module=ctx.module) ] ctx.depstack.append((alter_cmd, fq_name)) for cmd in subcmds: trace_dependencies(cmd, ctx=ctx) ctx.depstack.pop() if hard_dep_exprs: for expr in hard_dep_exprs: if isinstance(expr, qlast.TypeExpr): targets = qlast.get_targets(expr) for target in targets: if not target.subtypes: name = ctx.get_ref_name(target.maintype) if name.module == ctx.module: deps.add(name) else: for dep in qltracer.trace_refs( expr, schema=ctx.schema, module=ctx.module, source=source, path_prefix=source, subject=subject or fq_name, objects=ctx.objects, ): if dep.startswith(f"{ctx.module}::"): deps.add(dep) orig_op.commands = commands if loop_control: parent_node = ctx.ddlgraph[loop_control] if 'loop-control' not in parent_node: parent_node['loop-control'] = {fq_name} else: parent_node['loop-control'].add(fq_name) node["deps"].update(deps)
def _register_item( decl: qlast.DDLOperation, *, deps: Optional[AbstractSet[s_name.QualName]] = None, hard_dep_exprs: Optional[Iterable[Dependency]] = None, loop_control: Optional[s_name.QualName] = None, source: Optional[s_name.QualName] = None, subject: Optional[s_name.QualName] = None, ctx: DepTraceContext, ) -> None: name, fq_name = ctx.get_fq_name(decl) if deps: deps = set(deps) else: deps = set() op = orig_op = copy.copy(decl) if ctx.depstack: op.sdl_alter_if_exists = True top_parent = parent = copy.copy(ctx.depstack[0][0]) _clear_nonessential_subcommands(parent) for entry, _ in ctx.depstack[1:]: entry_op = copy.copy(entry) parent.commands.append(entry_op) parent = entry_op _clear_nonessential_subcommands(parent) parent.commands.append(op) op = top_parent else: op.aliases = [qlast.ModuleAliasDecl(alias=None, module=ctx.module)] assert isinstance(op, qlast.DDLCommand) node = topological.DepGraphEntry( item=op, deps={n for _, n in ctx.depstack if n != loop_control}, extra=False, ) ctx.ddlgraph[fq_name] = node if hasattr(decl, "bases"): # add parents to dependencies parents = ctx.parents.get(fq_name) if parents is not None: deps.update(parents) if ctx.depstack: # all ancestors should be seen as dependencies ancestor_bases = ctx.ancestors.get(ctx.depstack[-1][1]) if ancestor_bases: for ancestor_base in ancestor_bases: base_item = qltracer.qualify_name(ancestor_base, name) if base_item in ctx.objects: deps.add(base_item) ast_subcommands = getattr(decl, 'commands', []) commands = [] if ast_subcommands: subcmds: List[qlast.DDLOperation] = [] for cmd in ast_subcommands: # include dependency on constraints or annotations if present if isinstance(cmd, qlast.CreateConcreteConstraint): cmd_name = ctx.get_local_name( cmd.name, type=qltracer.Constraint) if cmd_name.get_module_name() not in s_schema.STD_MODULES: deps.add(cmd_name) elif isinstance(cmd, qlast.CreateAnnotationValue): cmd_name = ctx.get_local_name( cmd.name, type=qltracer.Annotation) if cmd_name.get_module_name() not in s_schema.STD_MODULES: deps.add(cmd_name) if (isinstance(cmd, qlast.ObjectDDL) # HACK: functions don't have alters at the moment and not isinstance(decl, qlast.CreateFunction)): subcmds.append(cmd) elif (isinstance(cmd, qlast.SetField) and not cmd.special_syntax and not isinstance(cmd.value, qlast.BaseConstant) and not isinstance(op, qlast.CreateAlias)): subcmds.append(cmd) else: commands.append(cmd) if subcmds: assert isinstance(decl, qlast.ObjectDDL) alter_name = f"Alter{decl.__class__.__name__[len('Create'):]}" alter_cls: Type[qlast.ObjectDDL] = getattr(qlast, alter_name) alter_cmd = alter_cls(name=decl.name) # indexes need to preserve their "on" expression if isinstance(decl, qlast.CreateIndex): alter_cmd.expr = decl.expr # constraints need to preserve their "on" expression if isinstance(decl, qlast.CreateConcreteConstraint): alter_cmd.subjectexpr = decl.subjectexpr alter_cmd.args = decl.args if not ctx.depstack: alter_cmd.aliases = [ qlast.ModuleAliasDecl(alias=None, module=ctx.module) ] ctx.depstack.append((alter_cmd, fq_name)) for cmd in subcmds: trace_dependencies(cmd, ctx=ctx) ctx.depstack.pop() if hard_dep_exprs: for expr in hard_dep_exprs: if isinstance(expr, TypeDependency): deps |= _get_hard_deps(expr.texpr, ctx=ctx) elif isinstance(expr, ExprDependency): qlexpr = expr.expr if isinstance(expr, FunctionDependency): params = expr.params else: params = {} tdeps = qltracer.trace_refs( qlexpr, schema=ctx.schema, module=ctx.module, source=source, path_prefix=source, subject=subject or fq_name, objects=ctx.objects, params=params, ) pdeps: MutableSet[s_name.QualName] = set() for dep in tdeps: # ignore std module dependencies if dep.get_module_name() not in s_schema.STD_MODULES: # First check if the dep is a pointer that's # defined explicitly. If it's not explicitly # defined, check for ancestors and use them # instead. # # FIXME: Ideally we should use the closest # ancestor, instead of all of them, but # including all is still correct. if '@' in dep.name: pdeps |= _get_pointer_deps(dep, ctx=ctx) else: pdeps.add(dep) # Handle the pre-processed deps now. for dep in pdeps: deps.add(dep) if isinstance(decl, qlast.CreateAlias): # If the declaration is a view, we need to be # dependent on all the types and their props # used in the view. vdeps = {dep} | ctx.ancestors.get(dep, set()) for vdep in vdeps: deps |= ctx.defdeps.get(vdep, set()) elif (isinstance(decl, qlast.CreateConcretePointer) and isinstance(decl.target, qlast.Expr)): # If the declaration is a computable # pointer, we need to include the possible # constraints for every dependency that it # lists. This is so that any other # links/props that this computable uses # has all of their constraints defined # before the computable and the # cardinality can be inferred correctly. cdeps = {dep} | ctx.ancestors.get(dep, set()) for cdep in cdeps: deps |= ctx.constraints.get(cdep, set()) else: raise AssertionError(f'unexpected dependency type: {expr!r}') orig_op.commands = commands if loop_control: parent_node = ctx.ddlgraph[loop_control] parent_node.loop_control.add(fq_name) node.deps |= deps
def _register_item(decl, *, deps=None, hard_dep_exprs=None, loop_control=None, source=None, subject=None, ctx: DepTraceContext): name, fq_name = ctx.get_fq_name(decl) if deps: deps = set(deps) else: deps = set() op = orig_op = copy.copy(decl) if ctx.depstack: op.sdl_alter_if_exists = True top_parent = parent = copy.copy(ctx.depstack[0][0]) _clear_nonessential_subcommands(parent) for entry, _entry_name in ctx.depstack[1:]: entry_op = copy.copy(entry) parent.commands.append(entry_op) parent = entry_op _clear_nonessential_subcommands(parent) parent.commands.append(op) op = top_parent else: op.aliases = [qlast.ModuleAliasDecl(alias=None, module=ctx.module)] node = { "item": op, "deps": {n for _, n in ctx.depstack if n != loop_control}, } ctx.ddlgraph[fq_name] = node if hasattr(decl, "bases"): # add parents to dependencies parents = ctx.parents.get(fq_name) if parents is not None: deps.update(parents) if ctx.depstack: # all ancestors should be seen as dependencies ancestor_bases = ctx.ancestors.get(ctx.depstack[-1][1]) if ancestor_bases: for ancestor_base in ancestor_bases: base_item = f'{ancestor_base}@{name}' if base_item in ctx.objects: deps.add(base_item) ast_subcommands = getattr(decl, 'commands', []) commands = [] if ast_subcommands: subcmds = [] for cmd in ast_subcommands: # include dependency on constraints or annotations if present if isinstance(cmd, qlast.CreateConcreteConstraint): cmd_name = ctx.get_local_name(cmd.name, type=qltracer.Constraint) if cmd_name.module not in s_schema.STD_MODULES: deps.add(cmd_name) elif isinstance(cmd, qlast.CreateAnnotationValue): cmd_name = ctx.get_local_name(cmd.name, type=qltracer.Annotation) if cmd_name.module not in s_schema.STD_MODULES: deps.add(cmd_name) if (isinstance(cmd, qlast.ObjectDDL) # HACK: functions don't have alters at the moment and not isinstance(decl, qlast.CreateFunction)): subcmds.append(cmd) elif (isinstance(cmd, qlast.SetField) and not isinstance(cmd.value, qlast.BaseConstant) and not isinstance(op, qlast.CreateAlias)): subcmds.append(cmd) else: commands.append(cmd) if subcmds: alter_name = f"Alter{decl.__class__.__name__[len('Create'):]}" alter_cls = getattr(qlast, alter_name) alter_cmd = alter_cls(name=decl.name) # indexes need to preserve their "on" expression if alter_name == 'AlterIndex': alter_cmd.expr = orig_op.expr for sub in orig_op.commands: if (isinstance(sub, qlast.SetField) and sub.name == 'orig_expr'): alter_cmd.commands.append(sub) break # constraints need to preserve their "on" expression elif alter_name == 'AlterConcreteConstraint': alter_cmd.subjectexpr = orig_op.subjectexpr alter_cmd.args = orig_op.args for sub in orig_op.commands: if (isinstance(sub, qlast.SetField) and sub.name == 'orig_subjectexpr'): alter_cmd.commands.append(sub) break if not ctx.depstack: alter_cmd.aliases = [ qlast.ModuleAliasDecl(alias=None, module=ctx.module) ] ctx.depstack.append((alter_cmd, fq_name)) for cmd in subcmds: trace_dependencies(cmd, ctx=ctx) ctx.depstack.pop() if hard_dep_exprs: for expr in hard_dep_exprs: if isinstance(expr, qlast.TypeExpr): deps |= _get_hard_deps(expr, ctx=ctx) else: if isinstance(expr, tuple): qlexpr, params = expr else: qlexpr = expr params = {} tdeps = qltracer.trace_refs( qlexpr, schema=ctx.schema, module=ctx.module, source=source, path_prefix=source, subject=subject or fq_name, objects=ctx.objects, params=params, ) for dep in tdeps: # ignore std module dependencies if not STD_PREFIX_RE.match(dep): deps.add(dep) if isinstance(decl, qlast.CreateAlias): # If the declaration is a view, we need to be # dependent on all the types and their props # used in the view. vdeps = {dep} | ctx.ancestors.get(dep, set()) for vdep in vdeps: deps |= ctx.defdeps.get(vdep, set()) elif (isinstance(decl, qlast.CreateConcretePointer) and isinstance(decl.target, qlast.Expr)): # If the declaration is a computable # pointer, we need to include the possible # constraints for every dependency that it # lists. This is so that any other # links/props that this computable uses # has all of their constraints defined # before the computable and the # cardinality can be inferred correctly. cdeps = {dep} | ctx.ancestors.get(dep, set()) for cdep in cdeps: deps |= ctx.constraints.get(cdep, set()) orig_op.commands = commands if loop_control: parent_node = ctx.ddlgraph[loop_control] if 'loop-control' not in parent_node: parent_node['loop-control'] = {fq_name} else: parent_node['loop-control'].add(fq_name) node["deps"].update(deps)