예제 #1
0
def _update_cardinality_in_derived(ptrcls: s_pointers.Pointer, *,
                                   env: context.Environment) -> None:

    children = env.pointer_derivation_map.get(ptrcls)
    if children:
        ptrcls_cardinality = ptrcls.get_cardinality(env.schema)
        ptrcls_required = ptrcls.get_required(env.schema)
        assert ptrcls_cardinality.is_known()
        for child in children:
            env.schema = child.set_field_value(env.schema, 'cardinality',
                                               ptrcls_cardinality)
            env.schema = child.set_field_value(env.schema, 'required',
                                               ptrcls_required)
            _update_cardinality_in_derived(child, env=env)
예제 #2
0
def _infer_pointer_cardinality(
    *,
    ptrcls: s_pointers.Pointer,
    ptrref: Optional[irast.BasePointerRef],
    irexpr: irast.Base,
    specified_required: Optional[bool] = None,
    specified_card: Optional[qltypes.SchemaCardinality] = None,
    is_mut_assignment: bool = False,
    shape_op: qlast.ShapeOp = qlast.ShapeOp.ASSIGN,
    source_ctx: Optional[parsing.ParserContext] = None,
    scope_tree: irast.ScopeTreeNode,
    ctx: inference_context.InfCtx,
) -> None:

    env = ctx.env

    if specified_required is None:
        spec_lower_bound = None
    else:
        spec_lower_bound = CardinalityBound.from_required(specified_required)

    if specified_card is None:
        spec_upper_bound = None
    else:
        spec_upper_bound = CardinalityBound.from_schema_value(specified_card)

    expr_card = infer_cardinality(irexpr, scope_tree=scope_tree, ctx=ctx)

    ptrcls_schema_card = ptrcls.get_cardinality(env.schema)

    # Infer cardinality and convert it back to schema values of "ONE/MANY".
    if shape_op is qlast.ShapeOp.APPEND:
        # += in shape always means MANY
        inferred_card = qltypes.Cardinality.MANY
    elif shape_op is qlast.ShapeOp.SUBTRACT:
        # -= does not increase cardinality, but it may result in an empty set,
        # hence AT_MOST_ONE.
        inferred_card = qltypes.Cardinality.AT_MOST_ONE
    else:
        # Pull cardinality from the ptrcls, if it exists.
        # (This generally will have been populated by the source_map
        # handling in infer_toplevel_cardinality().)
        if ptrcls_schema_card.is_known():
            inferred_card = qltypes.Cardinality.from_schema_value(
                not expr_card.can_be_zero(), ptrcls_schema_card)
        else:
            inferred_card = expr_card

    if spec_upper_bound is None and spec_lower_bound is None:
        # Common case of no explicit specifier and no overloading.
        ptr_card = inferred_card
    else:
        # Verify that the explicitly specified (or inherited) cardinality is
        # within the cardinality bounds inferred from the expression, except
        # for mutations we punt the lower cardinality bound check to the
        # runtime DML constraint as that would produce a more meaningful error.
        inf_lower_bound, inf_upper_bound = _card_to_bounds(inferred_card)

        if spec_upper_bound is None:
            upper_bound = inf_upper_bound
        else:
            if inf_upper_bound > spec_upper_bound:
                desc = ptrcls.get_verbosename(env.schema)
                if not is_mut_assignment:
                    desc = f"computed {desc}"
                raise errors.QueryError(
                    f"possibly more than one element returned by an "
                    f"expression for a {desc} declared as 'single'",
                    context=source_ctx,
                )
            upper_bound = spec_upper_bound

        if spec_lower_bound is None:
            lower_bound = inf_lower_bound
        else:
            if inf_lower_bound < spec_lower_bound:
                if is_mut_assignment:
                    lower_bound = inf_lower_bound
                else:
                    desc = f"computed {ptrcls.get_verbosename(env.schema)}"
                    raise errors.QueryError(
                        f"possibly an empty set returned by an "
                        f"expression for a {desc} declared as 'required'",
                        context=source_ctx,
                    )
            else:
                lower_bound = spec_lower_bound

        ptr_card = _bounds_to_card(lower_bound, upper_bound)

    if (not ptrcls_schema_card.is_known()
            or ptrcls in ctx.env.pointer_specified_info):
        if ptrcls_schema_card.is_known():
            # If we are overloading an existing pointer, take the _maximum_
            # of the cardinalities.  In practice this only means that we might
            # raise the lower bound, since the other redefinitions of bounds
            # are prohibited above and in viewgen.
            ptrcls_card = qltypes.Cardinality.from_schema_value(
                ptrcls.get_required(env.schema),
                ptrcls_schema_card,
            )
            if is_mut_assignment:
                ptr_card = cartesian_cardinality((ptrcls_card, ptr_card))
            else:
                ptr_card = max_cardinality((ptrcls_card, ptr_card))
        required, card = ptr_card.to_schema_value()
        env.schema = ptrcls.set_field_value(env.schema, 'cardinality', card)
        env.schema = ptrcls.set_field_value(env.schema, 'required', required)
        _update_cardinality_in_derived(ptrcls, env=ctx.env)

    if ptrref:
        out_card, in_card = typeutils.cardinality_from_ptrcls(
            env.schema, ptrcls)
        assert in_card is not None
        assert out_card is not None
        ptrref.in_cardinality = in_card
        ptrref.out_cardinality = out_card
예제 #3
0
def _infer_pointer_cardinality(
    *,
    ptrcls: s_pointers.Pointer,
    ptrref: irast.BasePointerRef,
    irexpr: irast.Base,
    specified_required: bool = False,
    specified_card: Optional[qltypes.SchemaCardinality] = None,
    is_mut_assignment: bool = False,
    shape_op: qlast.ShapeOp = qlast.ShapeOp.ASSIGN,
    source_ctx: Optional[parsing.ParserContext] = None,
    scope_tree: irast.ScopeTreeNode,
    ctx: inference_context.InfCtx,
) -> None:

    env = ctx.env

    # Convert the SchemaCardinality into Cardinality used for inference.
    if not specified_required and specified_card is None:
        ir_specified_card = None
    else:
        ir_specified_card = qltypes.Cardinality.from_schema_value(
            specified_required, specified_card
            or qltypes.SchemaCardinality.One)

    expr_card = infer_cardinality(irexpr, scope_tree=scope_tree, ctx=ctx)

    # Infer cardinality and convert it back to schema values of "ONE/MANY".
    if shape_op is qlast.ShapeOp.APPEND:
        # += in shape always means MANY
        inferred_card = qltypes.Cardinality.MANY
    elif shape_op is qlast.ShapeOp.SUBTRACT:
        # -= does not increase cardinality, but it may result in an empty set,
        # hence AT_MOST_ONE.
        inferred_card = qltypes.Cardinality.AT_MOST_ONE
    else:
        inferred_card = expr_card

    if ir_specified_card is None:
        ptr_card = inferred_card
    else:
        if is_subset_cardinality(inferred_card, ir_specified_card):
            # The inferred cardinality is within the boundaries of
            # specified cardinality, use the maximum lower and upper bounds.
            ptr_card = max_cardinality((ir_specified_card, inferred_card), )
        else:
            sp_req, sp_card = ir_specified_card.to_schema_value()
            ic_req, ic_card = inferred_card.to_schema_value()
            # Specified cardinality is stricter than inferred (e.g.
            # ONE vs MANY), this is an error.
            if sp_req and not ic_req:
                if is_mut_assignment:
                    # For mutations we punt the lower cardinality bound
                    # check to the runtime constraint.  Doing it statically
                    # is impractical because it is impossible to prove
                    # non-emptiness of object-selecting expressions bound
                    # for required links.
                    ptr_card = cartesian_cardinality(
                        (ir_specified_card, inferred_card), )
                else:
                    raise errors.QueryError(
                        f'possibly an empty set returned by an '
                        f'expression for a computable '
                        f'{ptrcls.get_verbosename(env.schema)} '
                        f"declared as 'required'",
                        context=source_ctx)
            else:
                raise errors.QueryError(
                    f'possibly more than one element returned by an '
                    f'expression for a computable '
                    f'{ptrcls.get_verbosename(env.schema)} '
                    f"declared as 'single'",
                    context=source_ctx)

    ptrcls_schema_card = ptrcls.get_cardinality(env.schema)
    if (not ptrcls_schema_card.is_known()
            or ptrcls in ctx.env.pointer_specified_info):
        if ptrcls_schema_card.is_known():
            # If we are overloading an existing pointer, take the _maximum_
            # of the cardinalities.  In practice this only means that we might
            # raise the lower bound, since the other redefinitions of bounds
            # are prohibited above and in viewgen.
            ptrcls_card = qltypes.Cardinality.from_schema_value(
                ptrcls.get_required(env.schema),
                ptrcls_schema_card,
            )
            if is_mut_assignment:
                ptr_card = cartesian_cardinality((ptrcls_card, ptr_card))
            else:
                ptr_card = max_cardinality((ptrcls_card, ptr_card))
        required, card = ptr_card.to_schema_value()
        env.schema = ptrcls.set_field_value(env.schema, 'cardinality', card)
        env.schema = ptrcls.set_field_value(env.schema, 'required', required)
        _update_cardinality_in_derived(ptrcls, env=ctx.env)

    out_card, dir_card = typeutils.cardinality_from_ptrcls(
        env.schema, ptrcls, direction=ptrref.direction)
    assert dir_card is not None
    assert out_card is not None
    ptrref.dir_cardinality = dir_card
    ptrref.out_cardinality = out_card