Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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,
            ))