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) assert ptrcls_cardinality.is_known() for child in children: env.schema = child.set_field_value(env.schema, 'cardinality', ptrcls_cardinality) _update_cardinality_in_derived(child, env=env)
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)
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)
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
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