Пример #1
0
def try_fold_associative_binop(
        opcall: irast.OperatorCall, *,
        ctx: context.ContextLevel) -> typing.Optional[irast.Set]:

    # Let's check if we have (CONST + (OTHER_CONST + X))
    # tree, which can be optimized to ((CONST + OTHER_CONST) + X)

    op = opcall.func_shortname
    my_const = opcall.args[0].expr
    other_binop = opcall.args[1].expr
    folded = None

    if isinstance(other_binop.expr, irast.BaseConstant):
        my_const, other_binop = other_binop, my_const

    if (isinstance(my_const.expr, irast.BaseConstant)
            and isinstance(other_binop.expr, irast.OperatorCall)
            and other_binop.expr.func_shortname == op
            and other_binop.expr.operator_kind is ft.OperatorKind.INFIX):

        other_const = other_binop.expr.args[0].expr
        other_binop_node = other_binop.expr.args[1].expr

        if isinstance(other_binop_node.expr, irast.BaseConstant):
            other_binop_node, other_const = \
                other_const, other_binop_node

        if isinstance(other_const.expr, irast.BaseConstant):
            try:
                new_const = ireval.evaluate(
                    irast.OperatorCall(
                        args=[
                            irast.CallArg(expr=other_const, ),
                            irast.CallArg(expr=my_const, ),
                        ],
                        func_module_id=opcall.func_module_id,
                        func_shortname=op,
                        func_polymorphic=opcall.func_polymorphic,
                        func_sql_function=opcall.func_sql_function,
                        sql_operator=opcall.sql_operator,
                        force_return_cast=opcall.force_return_cast,
                        operator_kind=opcall.operator_kind,
                        params_typemods=opcall.params_typemods,
                        context=opcall.context,
                        typeref=opcall.typeref,
                        typemod=opcall.typemod,
                    ),
                    schema=ctx.env.schema,
                )
            except ireval.UnsupportedExpressionError:
                pass
            else:
                folded_binop = irast.OperatorCall(
                    args=[
                        irast.CallArg(expr=setgen.ensure_set(new_const,
                                                             ctx=ctx), ),
                        irast.CallArg(expr=other_binop_node, ),
                    ],
                    func_module_id=opcall.func_module_id,
                    func_shortname=op,
                    func_polymorphic=opcall.func_polymorphic,
                    func_sql_function=opcall.func_sql_function,
                    sql_operator=opcall.sql_operator,
                    force_return_cast=opcall.force_return_cast,
                    operator_kind=opcall.operator_kind,
                    params_typemods=opcall.params_typemods,
                    context=opcall.context,
                    typeref=opcall.typeref,
                    typemod=opcall.typemod,
                )

                folded = setgen.ensure_set(folded_binop, ctx=ctx)

    return folded
Пример #2
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
Пример #3
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
Пример #4
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
Пример #5
0
def finalize_args(
    bound_call: polyres.BoundCall,
    *,
    actual_typemods: Sequence[ft.TypeModifier] = (),
    guessed_typemods: Dict[Union[int, str], 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
        arg_type_path_id: Optional[irast.PathId] = None
        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)

        if param_mod is not ft.TypeModifier.SetOfType:
            param_shortname = param.get_parameter_name(ctx.env.schema)

            if param_shortname in bound_call.null_args:
                pathctx.register_set_in_scope(arg, optional=True, ctx=ctx)

            # If we guessed the argument was optional but it wasn't,
            # try to go back and make it *not* optional.
            elif (param_mod is ft.TypeModifier.SingletonType
                  and barg.arg_id is not None
                  and param_mod is not guessed_typemods[barg.arg_id]):
                for child in ctx.path_scope.children:
                    if (child.path_id == arg.path_id
                            or (arg.path_scope_id is not None
                                and child.unique_id == arg.path_scope_id)):
                        child.optional = False

            # Object type arguments to functions may be overloaded, so
            # we populate a path id field so that we can also pass the
            # type as an argument on the pgsql side. If the param type
            # is "anytype", though, then it can't be overloaded on
            # that argument.
            arg_type = setgen.get_set_type(arg, ctx=ctx)
            if (isinstance(arg_type, s_objtypes.ObjectType) and barg.param
                    and not barg.param.get_type(ctx.env.schema).is_any(
                        ctx.env.schema)):
                arg_type_path_id = pathctx.extend_path_id(
                    arg.path_id,
                    ptrcls=arg_type.getptr(ctx.env.schema,
                                           sn.UnqualName('__type__')),
                    ctx=ctx,
                )
        else:
            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 = dispatch.compile(
                    qlast.IntegerConstant(value=str(ctx.implicit_limit)),
                    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,
                          expr_type_path_id=arg_type_path_id,
                          is_default=barg.is_default))

    return args, typemods