Ejemplo n.º 1
0
def finalize_args(
    bound_call: polyres.BoundCall,
    *,
    ctx: context.ContextLevel,
) -> typing.Tuple[typing.List[irast.CallArg], typing.List[ft.TypeModifier]]:

    args: typing.List[irast.CallArg] = []
    typemods = []

    for barg in bound_call.args:
        param = barg.param
        arg = barg.val
        if param is None:
            # defaults bitmask
            args.append(irast.CallArg(expr=arg))
            typemods.append(ft.TypeModifier.SINGLETON)
            continue

        param_mod = param.get_typemod(ctx.env.schema)
        typemods.append(param_mod)

        if param_mod is not ft.TypeModifier.SET_OF:
            arg_scope = pathctx.get_set_scope(arg, ctx=ctx)
            param_shortname = param.get_shortname(ctx.env.schema)

            # Arg was wrapped for scope fencing purposes,
            # but that fence has been removed above, so unwrap it.
            orig_arg = arg
            arg = irutils.unwrap_set(arg)

            if (param_mod is ft.TypeModifier.OPTIONAL
                    or param_shortname in bound_call.null_args):

                if arg_scope is not None:
                    # Due to the construction of relgen, the (unfenced)
                    # subscope is necessary to shield LHS paths from the outer
                    # query to prevent path binding which may break OPTIONAL.
                    branch = arg_scope.unfence()

                pathctx.register_set_in_scope(arg, ctx=ctx)
                pathctx.mark_path_as_optional(arg.path_id, ctx=ctx)

                if arg_scope is not None:
                    pathctx.assign_set_scope(arg, branch, ctx=ctx)

            elif arg_scope is not None:
                arg_scope.collapse()
                if arg is orig_arg:
                    pathctx.assign_set_scope(arg, None, ctx=ctx)

        paramtype = barg.param_type
        param_kind = param.get_kind(ctx.env.schema)
        if param_kind is ft.ParameterKind.VARIADIC:
            # For variadic params, paramtype would be array<T>,
            # and we need T to cast the arguments.
            assert isinstance(paramtype, s_types.Array)
            paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0]

        val_material_type = barg.valtype.material_type(ctx.env.schema)
        param_material_type = paramtype.material_type(ctx.env.schema)

        # Check if we need to cast the argument value before passing
        # it to the callable.  For tuples, we also check that the element
        # names match.
        compatible = (
            val_material_type.issubclass(ctx.env.schema, param_material_type)
            and (not param_material_type.is_tuple() or
                 (param_material_type.get_element_names(ctx.env.schema)
                  == val_material_type.get_element_names(ctx.env.schema))))

        if not compatible:
            # The callable form was chosen via an implicit cast,
            # cast the arguments so that the backend has no
            # wiggle room to apply its own (potentially different)
            # casting.
            arg = cast.compile_cast(arg, paramtype, srcctx=None, ctx=ctx)

        if param_mod is not ft.TypeModifier.SET_OF:
            call_arg = irast.CallArg(expr=arg, cardinality=ft.Cardinality.ONE)
        else:
            call_arg = irast.CallArg(expr=arg, cardinality=None)
            stmtctx.get_expr_cardinality_later(target=call_arg,
                                               field='cardinality',
                                               irexpr=arg,
                                               ctx=ctx)

        args.append(call_arg)

    return args, typemods
Ejemplo n.º 2
0
def finalize_args(
    bound_call: polyres.BoundCall, *,
    actual_typemods: Sequence[ft.TypeModifier] = (),
    is_polymorphic: bool = False,
    ctx: context.ContextLevel,
) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]:

    args: List[irast.CallArg] = []
    typemods = []

    for i, barg in enumerate(bound_call.args):
        param = barg.param
        arg = barg.val
        if param is None:
            # defaults bitmask
            args.append(irast.CallArg(expr=arg))
            typemods.append(ft.TypeModifier.SINGLETON)
            continue

        if actual_typemods:
            param_mod = actual_typemods[i]
        else:
            param_mod = param.get_typemod(ctx.env.schema)

        typemods.append(param_mod)

        if param_mod is not ft.TypeModifier.SET_OF:
            arg_scope = pathctx.get_set_scope(arg, ctx=ctx)
            param_shortname = param.get_parameter_name(ctx.env.schema)

            # Arg was wrapped for scope fencing purposes,
            # but that fence has been removed above, so unwrap it.
            orig_arg = arg
            arg = irutils.unwrap_set(arg)

            if (param_mod is ft.TypeModifier.OPTIONAL or
                    param_shortname in bound_call.null_args):

                if arg_scope is not None:
                    # Due to the construction of relgen, the (unfenced)
                    # subscope is necessary to shield LHS paths from the outer
                    # query to prevent path binding which may break OPTIONAL.
                    branch = arg_scope.unfence()

                pathctx.register_set_in_scope(arg, ctx=ctx)
                pathctx.mark_path_as_optional(arg.path_id, ctx=ctx)

                if arg_scope is not None:
                    pathctx.assign_set_scope(arg, branch, ctx=ctx)

            elif arg_scope is not None:
                arg_scope.collapse()
                if arg is orig_arg:
                    pathctx.assign_set_scope(arg, None, ctx=ctx)
        else:
            if (is_polymorphic
                    and ctx.expr_exposed
                    and ctx.implicit_limit
                    and isinstance(arg.expr, irast.SelectStmt)
                    and arg.expr.limit is None):
                arg.expr.limit = setgen.ensure_set(
                    dispatch.compile(
                        qlast.IntegerConstant(value=str(ctx.implicit_limit)),
                        ctx=ctx,
                    ),
                    ctx=ctx,
                )

        paramtype = barg.param_type
        param_kind = param.get_kind(ctx.env.schema)
        if param_kind is ft.ParameterKind.VARIADIC:
            # For variadic params, paramtype would be array<T>,
            # and we need T to cast the arguments.
            assert isinstance(paramtype, s_types.Array)
            paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0]

        # Check if we need to cast the argument value before passing
        # it to the callable.
        compatible = schemactx.is_type_compatible(
            paramtype,
            barg.valtype,
            ctx=ctx,
        )

        if not compatible:
            # The callable form was chosen via an implicit cast,
            # cast the arguments so that the backend has no
            # wiggle room to apply its own (potentially different)
            # casting.
            arg = casts.compile_cast(
                arg, paramtype, srcctx=None, ctx=ctx)

        call_arg = irast.CallArg(expr=arg, cardinality=None)
        stmtctx.get_expr_cardinality_later(
            target=call_arg, field='cardinality', irexpr=arg, ctx=ctx)

        args.append(call_arg)

    return args, typemods
Ejemplo n.º 3
0
def finalize_args(
    bound_call: polyres.BoundCall, *,
    arg_ctxs: Dict[irast.Set, context.ContextLevel],
    actual_typemods: Sequence[ft.TypeModifier] = (),
    is_polymorphic: bool = False,
    ctx: context.ContextLevel,
) -> Tuple[List[irast.CallArg], List[ft.TypeModifier]]:

    args: List[irast.CallArg] = []
    typemods = []

    for i, barg in enumerate(bound_call.args):
        param = barg.param
        arg = barg.val
        if param is None:
            # defaults bitmask
            args.append(irast.CallArg(expr=arg))
            typemods.append(ft.TypeModifier.SingletonType)
            continue

        if actual_typemods:
            param_mod = actual_typemods[i]
        else:
            param_mod = param.get_typemod(ctx.env.schema)

        typemods.append(param_mod)

        orig_arg = arg
        arg_ctx = arg_ctxs.get(orig_arg)
        arg_scope = pathctx.get_set_scope(arg, ctx=ctx)
        if param_mod is not ft.TypeModifier.SetOfType:
            param_shortname = param.get_parameter_name(ctx.env.schema)

            # Arg was wrapped for scope fencing purposes,
            # but that fence has been removed above, so unwrap it.
            arg = irutils.unwrap_set(arg)

            if (param_mod is ft.TypeModifier.OptionalType or
                    param_shortname in bound_call.null_args):

                process_path_log(arg_ctx, arg_scope)

                if arg_scope is not None:
                    # Due to the construction of relgen, the (unfenced)
                    # subscope is necessary to shield LHS paths from the outer
                    # query to prevent path binding which may break OPTIONAL.
                    arg_scope.mark_as_optional()
                    branch = arg_scope.unfence()

                pathctx.register_set_in_scope(arg, optional=True, ctx=ctx)

                if arg_scope is not None:
                    pathctx.assign_set_scope(arg, branch, ctx=ctx)

            elif arg_scope is not None:
                arg_scope.collapse()
                if arg is orig_arg:
                    pathctx.assign_set_scope(arg, None, ctx=ctx)
        else:
            process_path_log(arg_ctx, arg_scope)
            is_array_agg = (
                isinstance(bound_call.func, s_func.Function)
                and (
                    bound_call.func.get_shortname(ctx.env.schema)
                    == sn.QualName('std', 'array_agg')
                )
            )

            if (
                # Ideally, we should implicitly slice all array values,
                # but in practice, the vast majority of large arrays
                # will come from array_agg, and so we only care about
                # that.
                is_array_agg
                and ctx.expr_exposed
                and ctx.implicit_limit
                and isinstance(arg.expr, irast.SelectStmt)
                and arg.expr.limit is None
                and not ctx.inhibit_implicit_limit
            ):
                arg.expr.limit = setgen.ensure_set(
                    dispatch.compile(
                        qlast.IntegerConstant(value=str(ctx.implicit_limit)),
                        ctx=ctx,
                    ),
                    ctx=ctx,
                )

        paramtype = barg.param_type
        param_kind = param.get_kind(ctx.env.schema)
        if param_kind is ft.ParameterKind.VariadicParam:
            # For variadic params, paramtype would be array<T>,
            # and we need T to cast the arguments.
            assert isinstance(paramtype, s_types.Array)
            paramtype = list(paramtype.get_subtypes(ctx.env.schema))[0]

        # Check if we need to cast the argument value before passing
        # it to the callable.
        compatible = s_types.is_type_compatible(
            paramtype, barg.valtype, schema=ctx.env.schema,
        )

        if not compatible:
            # The callable form was chosen via an implicit cast,
            # cast the arguments so that the backend has no
            # wiggle room to apply its own (potentially different)
            # casting.
            arg = casts.compile_cast(
                arg, paramtype, srcctx=None, ctx=ctx)

        args.append(irast.CallArg(expr=arg, cardinality=None))

        # If we have any logged paths left over and our enclosing
        # context is logging paths, propagate them up.
        if arg_ctx and arg_ctx.path_log and ctx.path_log is not None:
            ctx.path_log.extend(arg_ctx.path_log)

    return args, typemods
Ejemplo n.º 4
0
def handle_conditional_insert(
    expr: qlast.InsertQuery,
    rhs: irast.InsertStmt,
    lhs_set: Union[irast.Expr, irast.Set],
    *,
    ctx: context.ContextLevel,
) -> irast.ConstraintRef:
    def error(s: str) -> NoReturn:
        raise errors.QueryError(
            f'Invalid conditional INSERT statement: {s}',
            context=expr.context,
        )

    schema = ctx.env.schema

    if not isinstance(lhs_set, irast.Set):
        error("left hand side is not SELECT")
    lhs_set = irutils.unwrap_set(lhs_set)
    if not isinstance(lhs_set.expr, irast.SelectStmt):
        error("left hand side is not SELECT")
    lhs = lhs_set.expr

    if lhs.result.path_id != rhs.subject.path_id:
        error("types do not match")

    if lhs.where:
        filtered_ptrs = inference.cardinality.extract_filters(
            lhs.result,
            lhs.where,
            scope_tree=ctx.path_scope,
            singletons=(),
            env=ctx.env,
            strict=True)
    else:
        filtered_ptrs = None

    # TODO: Can we support some >1 cases?
    if not filtered_ptrs or len(filtered_ptrs) > 1:
        error("does not contain exactly one FILTER clause")
        return None

    exclusive_constr: s_constr.Constraint = schema.get('std::exclusive')

    shape_props = {}
    for shape_set, _ in rhs.subject.shape:
        # We need to go through to the base_ptr to get at the
        # underlying type (instead of the shape's subtype)
        base_ptr = shape_set.rptr.ptrref.base_ptr
        if (not isinstance(base_ptr, irast.PointerRef)
                or not isinstance(shape_set.expr, irast.SelectStmt)):
            continue
        schema, pptr = typeutils.ptrcls_from_ptrref(base_ptr, schema=schema)
        shape_props[pptr] = shape_set.expr.result, base_ptr

    ptr, ptr_set = filtered_ptrs[0]

    ptr = ptr.get_nearest_non_derived_parent(schema)
    if ptr not in shape_props:
        error("property in FILTER clause does not match INSERT")
    result, rptr = shape_props[ptr]

    if not simple_stmt_eq(ptr_set.expr, result.expr, schema):
        error("value in FILTER clause does not match INSERT")

    ex_cnstrs = [
        c for c in ptr.get_constraints(schema).objects(schema)
        if c.issubclass(schema, exclusive_constr)
        and not c.get_subjectexpr(schema)
    ]

    if len(ex_cnstrs) != 1 or not ptr.is_property(schema):
        error("FILTER is not on an exclusive property")

    ex_cnstr = ex_cnstrs[0]

    module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id

    ctx.env.schema = schema
    return irast.ConstraintRef(id=ex_cnstr.id, module_id=module_id)