Esempio n. 1
0
def derive_ptr(ptr: s_pointers.Pointer,
               source: s_sources.Source,
               target: Optional[s_types.Type] = None,
               *qualifiers: str,
               derived_name: Optional[sn.SchemaName] = None,
               derived_name_quals: Optional[Sequence[str]] = (),
               derived_name_base: Optional[str] = None,
               preserve_shape: bool = False,
               preserve_path_id: bool = False,
               is_insert: bool = False,
               is_update: bool = False,
               inheritance_merge: bool = True,
               attrs: Optional[Dict[str, Any]] = None,
               ctx: context.ContextLevel) -> s_pointers.Pointer:

    if derived_name is None and ctx.derived_target_module:
        derived_name = derive_view_name(stype=ptr,
                                        derived_name_quals=derived_name_quals,
                                        derived_name_base=derived_name_base,
                                        ctx=ctx)

    if ptr.get_name(ctx.env.schema) == derived_name:
        qualifiers = qualifiers + (ctx.aliases.get('d'), )

    ctx.env.schema, derived = ptr.derive_ref(
        ctx.env.schema,
        source,
        *qualifiers,
        target=target,
        name=derived_name,
        inheritance_merge=inheritance_merge,
        inheritance_refdicts={'pointers'},
        mark_derived=True,
        transient=True,
        preserve_path_id=preserve_path_id,
        attrs=attrs)

    if not ptr.generic(ctx.env.schema):
        if isinstance(derived, s_sources.Source):
            ptr = cast(s_links.Link, ptr)
            scls_pointers = ptr.get_pointers(ctx.env.schema)
            derived_own_pointers = derived.get_pointers(ctx.env.schema)

            for pn, ptr in derived_own_pointers.items(ctx.env.schema):
                # This is a view of a view.  Make sure query-level
                # computable expressions for pointers are carried over.
                src_ptr = scls_pointers.get(ctx.env.schema, pn)
                # mypy somehow loses the type argument in the
                # "pointers" ObjectIndex.
                assert isinstance(src_ptr, s_pointers.Pointer)
                computable_data = ctx.source_map.get(src_ptr)
                if computable_data is not None:
                    ctx.source_map[ptr] = computable_data

    if preserve_shape and ptr in ctx.env.view_shapes:
        ctx.env.view_shapes[derived] = ctx.env.view_shapes[ptr]

    ctx.env.created_schema_objects.add(derived)

    return derived
Esempio n. 2
0
def derive_dummy_ptr(
    ptr: s_pointers.Pointer,
    *,
    ctx: context.ContextLevel,
) -> s_pointers.Pointer:
    stdobj = cast(s_objtypes.ObjectType, ctx.env.schema.get('std::Object'))
    derived_obj_name = stdobj.get_derived_name(ctx.env.schema,
                                               stdobj,
                                               module='__derived__')
    derived_obj = ctx.env.schema.get(derived_obj_name, None)
    if derived_obj is None:
        ctx.env.schema, derived_obj = stdobj.derive_subtype(
            ctx.env.schema, name=derived_obj_name)
        ctx.env.created_schema_objects.add(derived_obj)

    derived_name = ptr.get_derived_name(ctx.env.schema, derived_obj)

    derived: s_pointers.Pointer
    derived = cast(s_pointers.Pointer, ctx.env.schema.get(derived_name, None))
    if derived is None:
        ctx.env.schema, derived = ptr.derive_ref(ctx.env.schema,
                                                 derived_obj,
                                                 derived_obj,
                                                 attrs={
                                                     'cardinality':
                                                     qltypes.Cardinality.MANY,
                                                 },
                                                 name=derived_name,
                                                 mark_derived=True)
        ctx.env.created_schema_objects.add(derived)

    return derived
Esempio n. 3
0
def extend_path(
    source_set: irast.Set,
    ptrcls: s_pointers.Pointer,
    direction: PtrDir = PtrDir.Outbound,
    *,
    ignore_computable: bool = False,
    hoist_iterators: bool = False,
    unnest_fence: bool = False,
    same_computable_scope: bool = False,
    ctx: context.ContextLevel,
) -> irast.Set:
    """Return a Set node representing the new path tip."""

    if ptrcls.is_link_property(ctx.env.schema):
        src_path_id = source_set.path_id.ptr_path()
    else:
        if direction is not s_pointers.PointerDirection.Inbound:
            source = ptrcls.get_near_endpoint(ctx.env.schema, direction)
            assert isinstance(source, s_types.Type)
            stype = get_set_type(source_set, ctx=ctx)
            if not stype.issubclass(ctx.env.schema, source):
                # Polymorphic link reference
                source_set = type_intersection_set(
                    source_set, source, optional=True, ctx=ctx)

        src_path_id = source_set.path_id

    expr_type = get_set_type(source_set, ctx=ctx).get_expr_type(ctx.env.schema)
    path_id = pathctx.extend_path_id(
        src_path_id,
        ptrcls=ptrcls,
        direction=direction,
        ns=ctx.path_id_namespace,
        include_descendants_in_ptrref=expr_type is s_types.ExprType.Update,
        ctx=ctx,
    )

    target = ptrcls.get_far_endpoint(ctx.env.schema, direction)
    assert isinstance(target, s_types.Type)
    target_set = new_set(stype=target, path_id=path_id, ctx=ctx)

    ptr = irast.Pointer(
        source=source_set,
        target=target_set,
        direction=direction,
        ptrref=path_id.rptr(),
    )

    target_set.rptr = ptr
    is_computable = _is_computable_ptr(ptrcls, ctx=ctx)
    if not ignore_computable and is_computable:
        target_set = computable_ptr_set(
            ptr,
            unnest_fence=unnest_fence,
            hoist_iterators=hoist_iterators,
            same_computable_scope=same_computable_scope,
            ctx=ctx,
        )

    return target_set
Esempio n. 4
0
def extend_path(source_set: irast.Set,
                ptrcls: s_pointers.Pointer,
                direction: PtrDir = PtrDir.Outbound,
                target: typing.Optional[s_types.Type] = None,
                *,
                ignore_computable: bool = False,
                is_mut_assign: bool = False,
                unnest_fence: bool = False,
                same_computable_scope: bool = False,
                ctx: context.ContextLevel) -> irast.Set:
    """Return a Set node representing the new path tip."""

    if ptrcls.is_link_property(ctx.env.schema):
        src_path_id = source_set.path_id.ptr_path()
    else:
        if direction != s_pointers.PointerDirection.Inbound:
            source = ptrcls.get_near_endpoint(ctx.env.schema, direction)
            stype = get_set_type(source_set, ctx=ctx)
            if not stype.issubclass(ctx.env.schema, source):
                # Polymorphic link reference
                source_set = class_indirection_set(source_set,
                                                   source,
                                                   optional=True,
                                                   ctx=ctx)

        src_path_id = source_set.path_id

    if target is None:
        target = ptrcls.get_far_endpoint(ctx.env.schema, direction)
    path_id = pathctx.extend_path_id(src_path_id,
                                     ptrcls=ptrcls,
                                     direction=direction,
                                     target=target,
                                     ns=ctx.path_id_namespace,
                                     ctx=ctx)

    target_set = new_set(stype=target, path_id=path_id, ctx=ctx)

    ptr = irast.Pointer(
        source=source_set,
        target=target_set,
        direction=direction,
        ptrref=path_id.rptr(),
    )

    target_set.rptr = ptr
    is_computable = _is_computable_ptr(ptrcls,
                                       is_mut_assign=is_mut_assign,
                                       ctx=ctx)
    if not ignore_computable and is_computable:
        target_set = computable_ptr_set(
            ptr,
            unnest_fence=unnest_fence,
            from_default_expr=is_mut_assign,
            same_computable_scope=same_computable_scope,
            ctx=ctx,
        )

    return target_set
Esempio n. 5
0
def _infer_pointer_cardinality(
    *,
    ptrcls: s_pointers.Pointer,
    irexpr: irast.Set,
    specified_card: Optional[qltypes.Cardinality] = None,
    is_mut_assignment: bool = False,
    source_ctx: Optional[parsing.ParserContext] = None,
    ctx: context.ContextLevel,
) -> None:

    # Infer cardinality and convert it back to schema values of "ONE/MANY".
    inferred_card = infer_expr_cardinality(irexpr=irexpr, ctx=ctx)

    if specified_card is None:
        ptr_card = inferred_card
    else:
        if inf_card.is_subset_cardinality(inferred_card, specified_card):
            # The inferred cardinality is within the boundaries of
            # specified cardinality, use the maximum lower and upper bounds.
            ptr_card = inf_card.max_cardinality(
                (specified_card, inferred_card), )
        else:
            sp_req, sp_card = 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 = inf_card.cartesian_cardinality(
                        (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(ctx.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(ctx.env.schema)} '
                    f"declared as 'single'",
                    context=source_ctx)

    required, card = ptr_card.to_schema_value()
    ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality',
                                            card)
    ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'required',
                                            required)
    _update_cardinality_in_derived(ptrcls, ctx=ctx)
    _update_cardinality_callbacks(ptrcls, ctx=ctx)
Esempio n. 6
0
def derive_ptr(ptr: s_pointers.Pointer,
               source: s_sources.Source,
               target: typing.Optional[s_types.Type] = None,
               *qualifiers,
               derived_name: typing.Optional[sn.SchemaName] = None,
               derived_name_quals: typing.Optional[typing.Sequence[str]] = (),
               derived_name_base: typing.Optional[str] = None,
               preserve_shape: bool = False,
               preserve_path_id: bool = False,
               is_insert: bool = False,
               is_update: bool = False,
               inheritance_merge: bool = True,
               attrs: typing.Optional[dict] = None,
               ctx: context.ContextLevel) -> s_pointers.Pointer:

    if derived_name is None and ctx.derived_target_module:
        derived_name = derive_view_name(stype=ptr,
                                        derived_name_quals=derived_name_quals,
                                        derived_name_base=derived_name_base,
                                        ctx=ctx)

    if ptr.get_name(ctx.env.schema) == derived_name:
        qualifiers = qualifiers + (ctx.aliases.get('d'), )

    ctx.env.schema, derived = ptr.derive_ref(
        ctx.env.schema,
        source,
        target,
        *qualifiers,
        name=derived_name,
        inheritance_merge=inheritance_merge,
        refdict_whitelist={'pointers'},
        mark_derived=True,
        preserve_path_id=preserve_path_id,
        attrs=attrs)

    if not ptr.generic(ctx.env.schema):
        if isinstance(derived, s_sources.Source):
            ptr = typing.cast(s_links.Link, ptr)
            scls_pointers = ptr.get_pointers(ctx.env.schema)
            derived_own_pointers = derived.get_pointers(ctx.env.schema)

            for pn, ptr in derived_own_pointers.items(ctx.env.schema):
                # This is a view of a view.  Make sure query-level
                # computable expressions for pointers are carried over.
                src_ptr = scls_pointers.get(ctx.env.schema, pn)
                computable_data = ctx.source_map.get(src_ptr)
                if computable_data is not None:
                    ctx.source_map[ptr] = computable_data

    if preserve_shape and ptr in ctx.env.view_shapes:
        ctx.env.view_shapes[derived] = ctx.env.view_shapes[ptr]

    return derived
Esempio n. 7
0
def _get_base_ptr_cardinality(
    ptrcls: s_pointers.Pointer,
    *,
    ctx: context.ContextLevel,
) -> Optional[qltypes.SchemaCardinality]:
    ptr_name = ptrcls.get_name(ctx.env.schema)
    if ptr_name in {
        sn.QualName('std', 'link'),
        sn.QualName('std', 'property')
    }:
        return None
    else:
        return ptrcls.get_cardinality(ctx.env.schema)
Esempio n. 8
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)
Esempio n. 9
0
def _infer_pointer_cardinality(
        *,
        ptrcls: s_pointers.Pointer,
        irexpr: irast.Expr,
        specified_card: typing.Optional[qltypes.Cardinality] = None,
        source_ctx: typing.Optional[parsing.ParserContext] = None,
        ctx: context.ContextLevel) -> None:

    inferred_card = infer_expr_cardinality(irexpr=irexpr, ctx=ctx)
    if specified_card is None or inferred_card is specified_card:
        ptr_card = inferred_card
    else:
        if specified_card is qltypes.Cardinality.MANY:
            # Explicit many foo := <expr>, just take it.
            ptr_card = specified_card
        else:
            # Specified cardinality is ONE, but we inferred MANY, this
            # is an error.
            raise errors.QueryError(
                f'possibly more than one element returned by an '
                f'expression for a computable '
                f'{ptrcls.get_verbosename(ctx.env.schema)} '
                f"declared as 'single'",
                context=source_ctx
            )

    ctx.env.schema = ptrcls.set_field_value(
        ctx.env.schema, 'cardinality', ptr_card)
    _update_cardinality_in_derived(ptrcls, ctx=ctx)
    _update_cardinality_callbacks(ptrcls, ctx=ctx)
Esempio n. 10
0
def _infer_pointer_cardinality(
        *,
        ptrcls: s_pointers.Pointer,
        irexpr: irast.Set,
        specified_card: Optional[qltypes.Cardinality] = None,
        source_ctx: Optional[parsing.ParserContext] = None,
        ctx: context.ContextLevel) -> None:

    # Infer cardinality and convert it back to schema values of "ONE/MANY".
    inferred_card = infer_expr_cardinality(irexpr=irexpr, ctx=ctx)

    if specified_card is None:
        ptr_card = inferred_card
    else:
        if inf_card.is_subset_cardinality(inferred_card, specified_card):
            # The inferred cardinality is within the boundaries of
            # specified cardinality.
            ptr_card = specified_card
        else:
            sp_req, sp_card = 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:
                raise errors.QueryError(
                    f'possibly an empty set returned by an '
                    f'expression for a computable '
                    f'{ptrcls.get_verbosename(ctx.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(ctx.env.schema)} '
                    f"declared as 'single'",
                    context=source_ctx)

    required, card = ptr_card.to_schema_value()
    ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'cardinality',
                                            card)
    ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'required',
                                            required)
    _update_cardinality_in_derived(ptrcls, ctx=ctx)
    _update_cardinality_callbacks(ptrcls, ctx=ctx)
Esempio n. 11
0
def _update_cardinality_in_derived(
        ptrcls: s_pointers.Pointer, *,
        ctx: context.ContextLevel) -> None:

    children = ctx.pointer_derivation_map.get(ptrcls)
    if children:
        ptrcls_cardinality = ptrcls.get_cardinality(ctx.env.schema)
        for child in children:
            ctx.env.schema = child.set_field_value(
                ctx.env.schema, 'cardinality', ptrcls_cardinality)
            _update_cardinality_in_derived(child, ctx=ctx)
            _update_cardinality_callbacks(child, ctx=ctx)
Esempio n. 12
0
    def from_pointer(
            cls,
            schema: s_schema.Schema,
            pointer: s_pointers.Pointer,
            *,
            namespace: AbstractSet[AnyNamespace] = frozenset(),
    ) -> PathId:
        """Return a ``PathId`` instance for a given link or property.

        The specified *pointer* argument must be a concrete link or property.
        The returned ``PathId`` instance describes a set variable of all
        objects represented by the pointer (i.e, for a link, a set of all
        link targets).

        Args:
            schema:
                A schema instance where the type *t* is defined.
            pointer:
                An instance of a concrete link or property.
            namespace:
                Optional namespace in which the variable is defined.

        Returns:
            A ``PathId`` instance.
        """
        if pointer.generic(schema):
            raise ValueError(f'invalid PathId: {pointer} is not concrete')

        source = pointer.get_source(schema)
        if isinstance(source, s_pointers.Pointer):
            prefix = cls.from_pointer(schema, source, namespace=namespace)
            prefix = prefix.ptr_path()
        elif isinstance(source, s_types.Type):
            prefix = cls.from_type(schema, source, namespace=namespace)
        else:
            raise AssertionError(f'unexpected pointer source: {source!r}')

        ptrref = typeutils.ptrref_from_ptrcls(schema=schema, ptrcls=pointer)
        return prefix.extend(ptrref=ptrref)
Esempio n. 13
0
def _link_has_shape(ptrcls: s_pointers.Pointer, *,
                    ctx: context.ContextLevel) -> bool:
    if not isinstance(ptrcls, s_links.Link):
        return False

    for p in ptrcls.get_pointers(ctx.env.schema).objects(ctx.env.schema):
        if (p.is_special_pointer(ctx.env.schema)
                or p not in ctx.env.view_shapes[ptrcls]):
            continue
        else:
            return True

    return False
Esempio n. 14
0
def derive_dummy_ptr(
    ptr: s_pointers.Pointer,
    *,
    ctx: context.ContextLevel,
) -> s_pointers.Pointer:
    stdobj = ctx.env.schema.get('std::BaseObject', type=s_objtypes.ObjectType)
    derived_obj_name = stdobj.get_derived_name(ctx.env.schema,
                                               stdobj,
                                               module='__derived__')
    derived_obj = ctx.env.schema.get(derived_obj_name,
                                     None,
                                     type=s_obj.QualifiedObject)
    if derived_obj is None:
        ctx.env.schema, derived_obj = stdobj.derive_subtype(
            ctx.env.schema, name=derived_obj_name)
        ctx.env.created_schema_objects.add(derived_obj)

    derived_name = ptr.get_derived_name(ctx.env.schema, derived_obj)

    derived: s_pointers.Pointer
    derived = cast(s_pointers.Pointer, ctx.env.schema.get(derived_name, None))
    if derived is None:
        ctx.env.schema, derived = ptr.derive_ref(
            ctx.env.schema,
            derived_obj,
            target=derived_obj,
            attrs={
                'cardinality': qltypes.SchemaCardinality.One,
            },
            name=derived_name,
            mark_derived=True,
            transient=True,
        )
        ctx.env.created_schema_objects.add(derived)

    return derived
Esempio n. 15
0
def get_pointer_path_id(ptr: s_pointers.Pointer, *,
                        ctx: context.ContextLevel) -> irast.PathId:
    src = ptr.get_source(ctx.env.schema)
    assert isinstance(src, s_types.Type)
    return extend_path_id(get_path_id(src, ctx=ctx), ptrcls=ptr, ctx=ctx)
Esempio n. 16
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
Esempio n. 17
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