def compile_insert_unless_conflict( stmt: irast.InsertStmt, typ: s_objtypes.ObjectType, *, ctx: context.ContextLevel, ) -> irast.OnConflictClause: """Compile an UNLESS CONFLICT clause with no ON This requires synthesizing a conditional based on all the exclusive constraints on the object. """ pointers = _get_exclusive_ptr_constraints(typ, ctx=ctx) obj_constrs = typ.get_constraints(ctx.env.schema).objects(ctx.env.schema) select_ir, always_check, _ = compile_conflict_select( stmt, typ, constrs=pointers, obj_constrs=obj_constrs, parser_context=stmt.context, ctx=ctx) return irast.OnConflictClause( constraint=None, select_ir=select_ir, always_check=always_check, else_ir=None)
def compile_inheritance_conflict_selects( stmt: irast.MutatingStmt, conflict: irast.MutatingStmt, typ: s_objtypes.ObjectType, subject_type: s_objtypes.ObjectType, *, ctx: context.ContextLevel, ) -> List[irast.OnConflictClause]: """Compile the selects needed to resolve multiple DML to related types Generate a SELECT that finds all objects of type `typ` that conflict with the insert `stmt`. The backend will use this to explicitly check that no conflicts exist, and raise an error if they do. This is needed because we mostly use triggers to enforce these cross-type exclusive constraints, and they use a snapshot beginning at the start of the statement. """ pointers = _get_exclusive_ptr_constraints(typ, ctx=ctx) exclusive = ctx.env.schema.get('std::exclusive', type=s_constr.Constraint) obj_constrs = [ constr for constr in typ.get_constraints(ctx.env.schema).objects(ctx.env.schema) if constr.issubclass(ctx.env.schema, exclusive) ] shape_ptrs = set() for elem, op in stmt.subject.shape: assert elem.rptr is not None if op != qlast.ShapeOp.MATERIALIZE: shape_ptrs.add(elem.rptr.ptrref.shortname.name) # This is a little silly, but for *this* we need to do one per # constraint (so that we can properly identify which constraint # failed in the error messages) entries: List[Tuple[s_constr.Constraint, ConstraintPair]] = [] for name, (ptr, ptr_constrs) in pointers.items(): for ptr_constr in ptr_constrs: # For updates, we only need to emit the check if we actually # modify a pointer used by the constraint. For inserts, though # everything must be in play, since constraints can depend on # nonexistence also. if ( _constr_matters(ptr_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs(typ, (), [name], ctx)[0] & shape_ptrs) ) ): entries.append((ptr_constr, ({name: (ptr, [ptr_constr])}, []))) for obj_constr in obj_constrs: # See note above about needed ptrs check if ( _constr_matters(obj_constr, ctx) and ( isinstance(stmt, irast.InsertStmt) or (_get_needed_ptrs( typ, [obj_constr], (), ctx)[0] & shape_ptrs) ) ): entries.append((obj_constr, ({}, [obj_constr]))) # For updates, we need to pull from the actual result overlay, # since the final row can depend on things not in the query. fake_dml_set = None if isinstance(stmt, irast.UpdateStmt): fake_subject = qlast.DetachedExpr(expr=qlast.Path(steps=[ s_utils.name_to_ast_ref(subject_type.get_name(ctx.env.schema))])) fake_dml_set = dispatch.compile(fake_subject, ctx=ctx) clauses = [] for cnstr, (p, o) in entries: select_ir, _, _ = compile_conflict_select( stmt, typ, for_inheritance=True, fake_dml_set=fake_dml_set, constrs=p, obj_constrs=o, parser_context=stmt.context, ctx=ctx) if isinstance(select_ir, irast.EmptySet): continue cnstr_ref = irast.ConstraintRef(id=cnstr.id) clauses.append( irast.OnConflictClause( constraint=cnstr_ref, select_ir=select_ir, always_check=False, else_ir=None, else_fail=conflict, update_query_set=fake_dml_set) ) return clauses