def compile_insert_unless_conflict_select( stmt: irast.InsertStmt, insert_subject: qlast.Path, subject_typ: s_objtypes.ObjectType, *, obj_constrs: Sequence[s_constr.Constraint], constrs: Dict[str, Tuple[s_pointers.Pointer, List[s_constr.Constraint]]], parser_context: pctx.ParserContext, ctx: context.ContextLevel, ) -> irast.Set: """Synthesize a select of conflicting objects for UNLESS CONFLICT `cnstrs` contains the constraints to consider. """ # Find which pointers we need to grab needed_ptrs = set(constrs) for constr in obj_constrs: subjexpr = constr.get_subjectexpr(ctx.env.schema) assert subjexpr needed_ptrs |= qlutils.find_subject_ptrs(subjexpr.qlast) wl = list(needed_ptrs) ptr_anchors = {} while wl: p = wl.pop() ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) if expr := ptr.get_expr(ctx.env.schema): assert isinstance(expr.qlast, qlast.Expr) ptr_anchors[p] = expr.qlast for ref in qlutils.find_subject_ptrs(expr.qlast): if ref not in needed_ptrs: wl.append(ref) needed_ptrs.add(ref)
def _get_needed_ptrs( subject_typ: s_objtypes.ObjectType, obj_constrs: Sequence[s_constr.Constraint], initial_ptrs: Iterable[str], ctx: context.ContextLevel, ) -> Tuple[Set[str], Dict[str, qlast.Expr]]: needed_ptrs = set(initial_ptrs) for constr in obj_constrs: subjexpr = constr.get_subjectexpr(ctx.env.schema) assert subjexpr needed_ptrs |= qlutils.find_subject_ptrs(subjexpr.qlast) wl = list(needed_ptrs) ptr_anchors = {} while wl: p = wl.pop() ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) if expr := ptr.get_expr(ctx.env.schema): assert isinstance(expr.qlast, qlast.Expr) ptr_anchors[p] = expr.qlast for ref in qlutils.find_subject_ptrs(expr.qlast): if ref not in needed_ptrs: wl.append(ref) needed_ptrs.add(ref)
def _compile_conflict_select( stmt: irast.MutatingStmt, subject_typ: s_objtypes.ObjectType, *, for_inheritance: bool, fake_dml_set: Optional[irast.Set], obj_constrs: Sequence[s_constr.Constraint], constrs: Dict[str, Tuple[s_pointers.Pointer, List[s_constr.Constraint]]], parser_context: Optional[pctx.ParserContext], ctx: context.ContextLevel, ) -> Optional[qlast.Expr]: """Synthesize a select of conflicting objects ... for a single object type. This gets called once for each ancestor type that provides constraints to the type being inserted. `cnstrs` contains the constraints to consider. """ # Find which pointers we need to grab needed_ptrs, ptr_anchors = _get_needed_ptrs( subject_typ, obj_constrs, constrs.keys(), ctx=ctx ) ctx.anchors = ctx.anchors.copy() # If we are given a fake_dml_set to directly represent the result # of our DML, use that instead of populating the result. if fake_dml_set: for p in needed_ptrs | {'id'}: ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) val = setgen.extend_path(fake_dml_set, ptr, ctx=ctx) ptr_anchors[p] = ctx.create_anchor(val, p) # Find the IR corresponding to the fields we care about and # produce anchors for them ptrs_in_shape = set() for elem, _ in stmt.subject.shape: assert elem.rptr is not None name = elem.rptr.ptrref.shortname.name ptrs_in_shape.add(name) if name in needed_ptrs and name not in ptr_anchors: assert elem.expr if inference.infer_volatility(elem.expr, ctx.env).is_volatile(): if for_inheritance: error = ( 'INSERT does not support volatile properties with ' 'exclusive constraints when another statement in ' 'the same query modifies a related type' ) else: error = ( 'INSERT UNLESS CONFLICT ON does not support volatile ' 'properties' ) raise errors.UnsupportedFeatureError( error, context=parser_context ) # We want to use the same path_scope_id as the original elem_set = setgen.ensure_set(elem.expr, ctx=ctx) elem_set.path_scope_id = elem.path_scope_id # FIXME: The wrong thing will definitely happen if there are # volatile entries here ptr_anchors[name] = ctx.create_anchor(elem_set, name) if for_inheritance and not ptrs_in_shape: return None # Fill in empty sets for pointers that are needed but not present present_ptrs = set(ptr_anchors) for p in (needed_ptrs - present_ptrs): ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p)) typ = ptr.get_target(ctx.env.schema) assert typ ptr_anchors[p] = qlast.TypeCast( expr=qlast.Set(elements=[]), type=typegen.type_to_ql_typeref(typ, ctx=ctx)) if not ptr_anchors: raise errors.QueryError( 'INSERT UNLESS CONFLICT property requires matching shape', context=parser_context, ) conds: List[qlast.Expr] = [] for ptrname, (ptr, ptr_cnstrs) in constrs.items(): if ptrname not in present_ptrs: continue anchor = qlutils.subject_paths_substitute( ptr_anchors[ptrname], ptr_anchors) ptr_val = qlast.Path(partial=True, steps=[ qlast.Ptr(ptr=qlast.ObjectRef(name=ptrname)) ]) ptr, ptr_cnstrs = constrs[ptrname] ptr_card = ptr.get_cardinality(ctx.env.schema) for cnstr in ptr_cnstrs: lhs: qlast.Expr = anchor rhs: qlast.Expr = ptr_val # If there is a subjectexpr, substitute our lhs and rhs in # for __subject__ in the subjectexpr and compare *that* if (subjectexpr := cnstr.get_subjectexpr(ctx.env.schema)): assert isinstance(subjectexpr.qlast, qlast.Expr) lhs = qlutils.subject_substitute(subjectexpr.qlast, lhs) rhs = qlutils.subject_substitute(subjectexpr.qlast, rhs) conds.append(qlast.BinOp( op='=' if ptr_card.is_single() else 'IN', left=lhs, right=rhs, ))