Esempio n. 1
0
def compile_Coalesce(expr: qlast.Base, *,
                     ctx: context.ContextLevel) -> irast.Base:
    if all(isinstance(a, qlast.Set) and not a.elements for a in expr.args):
        return irutils.new_empty_set(ctx.schema, alias=ctx.aliases.get('e'))

    with ctx.newscope() as newctx:
        leftmost_arg = larg = setgen.ensure_set(dispatch.compile(expr.args[0],
                                                                 ctx=newctx),
                                                ctx=newctx)

        for rarg_ql in expr.args[1:]:
            with newctx.new() as nestedscopectx:
                with nestedscopectx.newscope(fenced=True) as fencectx:
                    rarg = setgen.scoped_set(dispatch.compile(rarg_ql,
                                                              ctx=fencectx),
                                             ctx=fencectx)

                coalesce = irast.Coalesce(left=larg, right=rarg)
                larg = setgen.generated_set(coalesce, ctx=nestedscopectx)

        # Make sure any empty set types are properly resolved
        # before entering them into the scope tree.
        irutils.infer_type(larg, schema=ctx.schema)

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

    return larg
Esempio n. 2
0
def compile_Set(
        expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base:
    if expr.elements:
        if len(expr.elements) == 1:
            # From the scope perspective, single-element set
            # literals are equivalent to a binary UNION with
            # an empty set, not to the element.
            with ctx.newscope(fenced=True) as scopectx:
                ir_set = dispatch.compile(expr.elements[0], ctx=scopectx)
                return setgen.scoped_set(ir_set, ctx=scopectx)
        else:
            elements = flatten_set(expr)
            # a set literal is just sugar for a UNION
            op = qlast.UNION

            bigunion = qlast.BinOp(
                left=elements[0],
                right=elements[1],
                op=op
            )
            for el in elements[2:]:
                bigunion = qlast.BinOp(
                    left=bigunion,
                    right=el,
                    op=op
                )
            return dispatch.compile(bigunion, ctx=ctx)
    else:
        return irutils.new_empty_set(ctx.schema, alias=ctx.aliases.get('e'))
Esempio n. 3
0
def _cast_expr(ql_type: qlast.TypeName, ir_expr: irast.Base, *,
               source_context: parsing.ParserContext,
               ctx: context.ContextLevel) -> irast.Base:
    try:
        orig_type = irutils.infer_type(ir_expr, ctx.schema)
    except errors.EdgeQLError:
        # It is possible that the source expression is unresolved
        # if the expr is an empty set (or a coalesce of empty sets).
        orig_type = None

    if isinstance(orig_type, s_types.Tuple):
        # For tuple-to-tuple casts we generate a new tuple
        # to simplify things on sqlgen side.
        new_type = typegen.ql_typeref_to_type(ql_type, ctx=ctx)
        if not isinstance(new_type, s_types.Tuple):
            raise errors.EdgeQLError(f'cannot cast tuple to {new_type.name}',
                                     context=source_context)

        if len(orig_type.element_types) != len(new_type.element_types):
            raise errors.EdgeQLError(
                f'cannot cast to {new_type.name}: '
                f'number of elements is not the same',
                context=source_context)

        new_names = list(new_type.element_types)

        elements = []
        for i, n in enumerate(orig_type.element_types):
            val = setgen.generated_set(irast.TupleIndirection(expr=ir_expr,
                                                              name=n),
                                       ctx=ctx)
            val.path_id = irutils.tuple_indirection_path_id(
                ir_expr.path_id, n, orig_type.element_types[n])

            val_type = irutils.infer_type(val, ctx.schema)
            new_el_name = new_names[i]
            if val_type != new_type.element_types[new_el_name]:
                # Element cast
                val = _cast_expr(ql_type.subtypes[i],
                                 val,
                                 ctx=ctx,
                                 source_context=source_context)

            elements.append(irast.TupleElement(name=new_el_name, val=val))

        return irast.Tuple(named=new_type.named, elements=elements)

    elif isinstance(ir_expr, irast.EmptySet):
        # For the common case of casting an empty set, we simply
        # generate a new EmptySet node of the requested type.
        scls = typegen.ql_typeref_to_type(ql_type, ctx=ctx)
        return irutils.new_empty_set(ctx.schema,
                                     scls=scls,
                                     alias=ir_expr.path_id.target.name.name)

    else:
        typ = typegen.ql_typeref_to_ir_typeref(ql_type, ctx=ctx)
        return setgen.ensure_set(irast.TypeCast(expr=ir_expr, type=typ),
                                 ctx=ctx)
Esempio n. 4
0
def compile_Coalesce(expr: qlast.Base, *,
                     ctx: context.ContextLevel) -> irast.Base:
    if all(isinstance(a, qlast.Set) and not a.elements for a in expr.args):
        return irutils.new_empty_set(ctx.env.schema,
                                     alias=ctx.aliases.get('e'))

    # Due to the construction of relgen, the (unfenced) subscope
    # below is necessary to shield LHS paths from the outer query
    # to prevent path binding which may break OPTIONAL.
    with ctx.newscope() as newctx:
        leftmost_arg = larg = setgen.ensure_set(dispatch.compile(expr.args[0],
                                                                 ctx=newctx),
                                                ctx=newctx)

        for rarg_ql in expr.args[1:]:
            with newctx.new() as nestedscopectx:
                with nestedscopectx.newscope(fenced=True) as fencectx:
                    rarg = setgen.scoped_set(dispatch.compile(rarg_ql,
                                                              ctx=fencectx),
                                             force_reassign=True,
                                             ctx=fencectx)

                coalesce = irast.Coalesce(left=larg, right=rarg)
                larg = setgen.generated_set(coalesce, ctx=nestedscopectx)

            stmtctx.get_expr_cardinality_later(target=coalesce,
                                               field='right_card',
                                               irexpr=rarg,
                                               ctx=ctx)

        # Make sure any empty set types are properly resolved
        # before entering them into the scope tree.
        inference.infer_type(larg, env=ctx.env)

        pathctx.register_set_in_scope(leftmost_arg, ctx=ctx)
        pathctx.mark_path_as_optional(leftmost_arg.path_id, ctx=ctx)
        pathctx.assign_set_scope(leftmost_arg, newctx.path_scope, ctx=ctx)

    return larg
Esempio n. 5
0
def compile_Coalesce(expr: qlast.Base, *,
                     ctx: context.ContextLevel) -> irast.Base:
    if all(isinstance(a, qlast.Set) and not a.elements for a in expr.args):
        return irutils.new_empty_set(ctx.schema, alias=ctx.aliases.get('e'))

    with ctx.new() as newctx:
        larg = setgen.ensure_set(dispatch.compile(expr.args[0], ctx=newctx),
                                 ctx=newctx)
        lcard = irinference.infer_cardinality(larg,
                                              singletons=newctx.singletons,
                                              schema=newctx.schema)

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

        for rarg_ql in expr.args[1:]:
            with newctx.new() as nestedscopectx:
                with nestedscopectx.newscope(fenced=True) as fencectx:
                    rarg = setgen.scoped_set(dispatch.compile(rarg_ql,
                                                              ctx=fencectx),
                                             ctx=fencectx)
                    rcard = irinference.infer_cardinality(
                        rarg,
                        singletons=fencectx.singletons,
                        schema=fencectx.schema)

                coalesce = irast.Coalesce(left=larg,
                                          lcardinality=lcard,
                                          right=rarg,
                                          rcardinality=rcard)
                larg = setgen.generated_set(coalesce, ctx=nestedscopectx)
                lcard = irinference.infer_cardinality(
                    larg,
                    singletons=nestedscopectx.singletons,
                    schema=nestedscopectx.schema)

    return larg
Esempio n. 6
0
def try_bind_call_args(args: typing.List[typing.Tuple[s_types.Type,
                                                      irast.Base]],
                       kwargs: typing.Dict[str, typing.Tuple[s_types.Type,
                                                             irast.Base]],
                       func: s_func.CallableObject, *,
                       ctx: context.ContextLevel) -> BoundCall:
    def _get_cast_distance(arg, arg_type, param_type) -> int:
        nonlocal resolved_poly_base_type

        if in_polymorphic_func:
            # Compiling a body of a polymorphic function.

            if arg_type.is_polymorphic(schema):
                if param_type.is_polymorphic(schema):
                    if arg_type.test_polymorphic(schema, param_type):
                        return 0
                    else:
                        return -1
                else:
                    if arg_type.resolve_polymorphic(schema, param_type):
                        return 0
                    else:
                        return -1

        else:
            if arg_type.is_polymorphic(schema):
                raise errors.QueryError(
                    f'a polymorphic argument in a non-polymorphic function',
                    context=arg.context)

        if param_type.is_polymorphic(schema):
            if not arg_type.test_polymorphic(schema, param_type):
                return -1

            resolved = param_type.resolve_polymorphic(schema, arg_type)
            if resolved is None:
                return -1

            if resolved_poly_base_type is None:
                resolved_poly_base_type = resolved

            if resolved_poly_base_type == resolved:
                return 0

            ct = resolved_poly_base_type.find_common_implicitly_castable_type(
                resolved, ctx.env.schema)

            return 0 if ct is not None else -1

        if arg_type.issubclass(schema, param_type):
            return 0

        return arg_type.get_implicit_cast_distance(param_type, schema)

    schema = ctx.env.schema

    in_polymorphic_func = (ctx.func is not None and
                           ctx.func.get_params(schema).has_polymorphic(schema))

    has_empty_variadic = False
    resolved_poly_base_type = None
    no_args_call = not args and not kwargs
    has_inlined_defaults = func.has_inlined_defaults(schema)

    func_params = func.get_params(schema)

    if not func_params:
        if no_args_call:
            # Match: `func` is a function without parameters
            # being called with no arguments.
            args = []
            if has_inlined_defaults:
                bytes_t = schema.get('std::bytes')
                argval = setgen.ensure_set(irast.BytesConstant(value='\x00',
                                                               stype=bytes_t),
                                           typehint=bytes_t,
                                           ctx=ctx)
                args = [BoundArg(None, argval, bytes_t, 0)]
            return BoundCall(func, args, set(), func.get_return_type(schema),
                             False)
        else:
            # No match: `func` is a function without parameters
            # being called with some arguments.
            return _NO_MATCH

    pg_params = s_func.PgParams.from_params(schema, func_params)
    named_only = func_params.find_named_only(schema)

    if no_args_call and pg_params.has_param_wo_default:
        # A call without arguments and there is at least
        # one parameter without default.
        return _NO_MATCH

    bound_param_args = []

    params = pg_params.params
    nparams = len(params)
    nargs = len(args)
    has_missing_args = False

    ai = 0
    pi = 0
    matched_kwargs = 0

    # Bind NAMED ONLY arguments (they are compiled as first set of arguments).
    while True:
        if pi >= nparams:
            break

        param = params[pi]
        if param.get_kind(schema) is not _NAMED_ONLY:
            break

        pi += 1

        param_shortname = param.get_shortname(schema)
        if param_shortname in kwargs:
            matched_kwargs += 1

            arg_type, arg_val = kwargs[param_shortname]
            cd = _get_cast_distance(arg_val, arg_type, param.get_type(schema))
            if cd < 0:
                return _NO_MATCH

            bound_param_args.append(BoundArg(param, arg_val, arg_type, cd))

        else:
            if param.get_default(schema) is None:
                # required named parameter without default and
                # without a matching argument
                return _NO_MATCH

            has_missing_args = True
            bound_param_args.append(
                BoundArg(param, None, param.get_type(schema), 0))

    if matched_kwargs != len(kwargs):
        # extra kwargs?
        return _NO_MATCH

    # Bind POSITIONAL arguments (compiled to go after NAMED ONLY arguments).
    while True:
        if ai < nargs:
            arg_type, arg_val = args[ai]
            ai += 1

            if pi >= nparams:
                # too many positional arguments
                return _NO_MATCH
            param = params[pi]
            param_kind = param.get_kind(schema)
            pi += 1

            if param_kind is _NAMED_ONLY:
                # impossible condition
                raise RuntimeError('unprocessed NAMED ONLY parameter')

            if param_kind is _VARIADIC:
                var_type = param.get_type(schema).get_subtypes()[0]
                cd = _get_cast_distance(arg_val, arg_type, var_type)
                if cd < 0:
                    return _NO_MATCH

                bound_param_args.append(BoundArg(param, arg_val, arg_type, cd))

                for arg_type, arg_val in args[ai:]:
                    cd = _get_cast_distance(arg_val, arg_type, var_type)
                    if cd < 0:
                        return _NO_MATCH

                    bound_param_args.append(
                        BoundArg(param, arg_val, arg_type, cd))

                break

            cd = _get_cast_distance(arg_val, arg_type, param.get_type(schema))
            if cd < 0:
                return _NO_MATCH

            bound_param_args.append(BoundArg(param, arg_val, arg_type, cd))

        else:
            break

    # Handle yet unprocessed POSITIONAL & VARIADIC arguments.
    for pi in range(pi, nparams):
        param = params[pi]
        param_kind = param.get_kind(schema)

        if param_kind is _POSITIONAL:
            if param.get_default(schema) is None:
                # required positional parameter that we don't have a
                # positional argument for.
                return _NO_MATCH

            has_missing_args = True
            param_type = param.get_type(schema)
            bound_param_args.append(BoundArg(param, None, param_type, 0))

        elif param_kind is _VARIADIC:
            has_empty_variadic = True

        elif param_kind is _NAMED_ONLY:
            # impossible condition
            raise RuntimeError('unprocessed NAMED ONLY parameter')

    # Populate defaults.
    defaults_mask = 0
    null_args = set()
    if has_missing_args:
        if has_inlined_defaults or named_only:
            for i in range(len(bound_param_args)):
                barg = bound_param_args[i]
                if barg.val is not None:
                    continue

                param = barg.param
                param_shortname = param.get_shortname(schema)
                null_args.add(param_shortname)

                defaults_mask |= 1 << i

                if not has_inlined_defaults:
                    ql_default = param.get_ql_default(schema)
                    default = dispatch.compile(ql_default, ctx=ctx)

                empty_default = (has_inlined_defaults
                                 or irutils.is_empty(default))

                param_type = param.get_type(schema)

                if empty_default:
                    default_type = None

                    if param_type.is_any():
                        if resolved_poly_base_type is None:
                            raise errors.QueryError(
                                f'could not resolve "anytype" type for the '
                                f'${param_shortname} parameter')
                        else:
                            default_type = resolved_poly_base_type
                    else:
                        default_type = param_type

                else:
                    default_type = param_type

                if has_inlined_defaults:
                    default = irutils.new_empty_set(schema,
                                                    stype=default_type,
                                                    alias=param_shortname)

                default = setgen.ensure_set(default,
                                            typehint=default_type,
                                            ctx=ctx)

                bound_param_args[i] = BoundArg(
                    param,
                    default,
                    barg.valtype,
                    barg.cast_distance,
                )

        else:
            bound_param_args = [
                barg for barg in bound_param_args if barg.val is not None
            ]

    if has_inlined_defaults:
        # If we are compiling an EdgeQL function, inject the defaults
        # bit-mask as a first argument.
        bytes_t = schema.get('std::bytes')
        bm = defaults_mask.to_bytes(nparams // 8 + 1, 'little')
        bm_set = setgen.ensure_set(irast.BytesConstant(
            value=bm.decode('ascii'), stype=bytes_t),
                                   typehint=bytes_t,
                                   ctx=ctx)
        bound_param_args.insert(0, BoundArg(None, bm_set, bytes_t, 0))

    return_type = func.get_return_type(schema)
    if return_type.is_polymorphic(schema):
        if resolved_poly_base_type is not None:
            return_type = return_type.to_nonpolymorphic(
                schema, resolved_poly_base_type)
        elif not in_polymorphic_func:
            return _NO_MATCH

    return BoundCall(func, bound_param_args, null_args, return_type,
                     has_empty_variadic)
Esempio n. 7
0
def compile_result_clause(
        result: qlast.Base, *,
        view_scls: typing.Optional[s_types.Type]=None,
        view_rptr: typing.Optional[context.ViewRPtr]=None,
        view_name: typing.Optional[s_name.SchemaName]=None,
        result_alias: typing.Optional[str]=None,
        forward_rptr: bool=False,
        ctx: context.ContextLevel) -> irast.Set:
    with ctx.new() as sctx:
        sctx.clause = 'result'
        if sctx.stmt is ctx.toplevel_stmt:
            sctx.toplevel_clause = sctx.clause
            sctx.expr_exposed = True

        if forward_rptr:
            sctx.view_rptr = view_rptr
            # sctx.view_scls = view_scls

        if isinstance(result, qlast.Shape):
            result_expr = result.expr
            shape = result.elements
        else:
            result_expr = result
            shape = None

        if result_alias:
            # `SELECT foo := expr` is largely equivalent to
            # `WITH foo := expr SELECT foo` with one important exception:
            # the scope namespace does not get added to the current query
            # path scope.  This is needed to handle FOR queries correctly.
            with sctx.newscope(temporary=True, fenced=True) as scopectx:
                stmtctx.declare_view(
                    result_expr, alias=result_alias,
                    temporary_scope=False, ctx=scopectx)

            result_expr = qlast.Path(
                steps=[qlast.ObjectRef(name=result_alias)]
            )

        if (view_rptr is not None and
                (view_rptr.is_insert or view_rptr.is_update) and
                view_rptr.ptrcls is not None) and False:
            # If we have an empty set assigned to a pointer in an INSERT
            # or UPDATE, there's no need to explicitly specify the
            # empty set type and it can be assumed to match the pointer
            # target type.
            target_t = view_rptr.ptrcls.target

            if astutils.is_ql_empty_set(result_expr):
                expr = irutils.new_empty_set(
                    sctx.schema, scls=target_t,
                    alias=ctx.aliases.get('e'))
            else:
                with sctx.new() as exprctx:
                    exprctx.empty_result_type_hint = target_t
                    expr = setgen.ensure_set(
                        dispatch.compile(result_expr, ctx=exprctx),
                        ctx=exprctx)
        else:
            if astutils.is_ql_empty_set(result_expr):
                expr = irutils.new_empty_set(
                    sctx.schema, scls=sctx.empty_result_type_hint,
                    alias=ctx.aliases.get('e'))
            else:
                expr = setgen.ensure_set(
                    dispatch.compile(result_expr, ctx=sctx), ctx=sctx)

        result = compile_query_subject(
            expr, shape=shape, view_rptr=view_rptr, view_name=view_name,
            result_alias=result_alias,
            view_scls=view_scls,
            compile_views=ctx.stmt is ctx.toplevel_stmt,
            ctx=sctx)

        ctx.partial_path_prefix = result

    return result
Esempio n. 8
0
def compile_cast(ir_expr: irast.Base, new_stype: s_types.Type, *,
                 srcctx: parsing.ParserContext,
                 ctx: context.ContextLevel) -> irast.OperatorCall:

    if isinstance(ir_expr, irast.EmptySet):
        # For the common case of casting an empty set, we simply
        # generate a new EmptySet node of the requested type.
        return irutils.new_empty_set(ctx.env.schema,
                                     stype=new_stype,
                                     alias=ir_expr.path_id.target_name.name)

    elif irutils.is_untyped_empty_array_expr(ir_expr):
        # Ditto for empty arrays.
        return setgen.generated_set(irast.Array(elements=[], stype=new_stype),
                                    ctx=ctx)

    ir_set = setgen.ensure_set(ir_expr, ctx=ctx)
    orig_stype = ir_set.stype

    if orig_stype == new_stype:
        return ir_set
    elif orig_stype.is_object_type() and new_stype.is_object_type():
        # Object types cannot be cast between themselves,
        # as cast is a _constructor_ operation, and the only
        # valid way to construct an object is to INSERT it.
        raise errors.QueryError(
            f'cannot cast object type '
            f'{orig_stype.get_displayname(ctx.env.schema)!r} '
            f'to {new_stype.get_displayname(ctx.env.schema)!r}, use '
            f'`...[IS {new_stype.get_displayname(ctx.env.schema)}]` instead',
            context=srcctx)

    if isinstance(ir_set.expr, irast.Array):
        return _cast_array_literal(ir_set,
                                   orig_stype,
                                   new_stype,
                                   srcctx=srcctx,
                                   ctx=ctx)

    elif orig_stype.is_tuple():
        return _cast_tuple(ir_set,
                           orig_stype,
                           new_stype,
                           srcctx=srcctx,
                           ctx=ctx)

    elif orig_stype.issubclass(ctx.env.schema, new_stype):
        # The new type is a supertype of the old type,
        # and is always a wider domain, so we simply reassign
        # the stype.
        return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, ctx=ctx)

    elif new_stype.issubclass(ctx.env.schema, orig_stype):
        # The new type is a subtype, so may potentially have
        # a more restrictive domain, generate a cast call.
        return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, ctx=ctx)

    elif orig_stype.is_array():
        return _cast_array(ir_set,
                           orig_stype,
                           new_stype,
                           srcctx=srcctx,
                           ctx=ctx)

    else:
        json_t = ctx.env.schema.get('std::json')

        if (new_stype.issubclass(ctx.env.schema, json_t)
                and ir_set.path_id.is_objtype_path()):
            # JSON casts of objects are special: we want the full shape
            # and not just an identity.
            viewgen.compile_view_shapes(ir_set, ctx=ctx)

        return _compile_cast(ir_expr,
                             orig_stype,
                             new_stype,
                             srcctx=srcctx,
                             ctx=ctx)