Beispiel #1
0
def new_tuple_set(elements: List[irast.TupleElement], *, named: bool,
                  ctx: context.ContextLevel) -> irast.Set:

    tup = irast.Tuple(elements=elements, named=named)
    stype = inference.infer_type(tup, env=ctx.env)
    result_path_id = pathctx.get_expression_path_id(stype, ctx=ctx)

    final_elems = []
    for elem in elements:
        elem_path_id = pathctx.get_tuple_indirection_path_id(result_path_id,
                                                             elem.name,
                                                             get_set_type(
                                                                 elem.val,
                                                                 ctx=ctx),
                                                             ctx=ctx)
        final_elems.append(
            irast.TupleElement(
                name=elem.name,
                val=elem.val,
                path_id=elem_path_id,
            ))

    typeref = typegen.type_to_typeref(stype, env=ctx.env)
    final_tup = irast.Tuple(elements=final_elems, named=named, typeref=typeref)
    return ensure_set(final_tup,
                      path_id=result_path_id,
                      type_override=stype,
                      ctx=ctx)
Beispiel #2
0
def _cast_json_to_tuple(
        ir_set: irast.Set,
        orig_stype: s_types.Type,
        new_stype: s_types.Tuple,
        cardinality_mod: Optional[qlast.CardinalityModifier],
        *,
        srcctx: Optional[parsing.ParserContext],
        ctx: context.ContextLevel) -> irast.Set:

    with ctx.new() as subctx:
        subctx.anchors = subctx.anchors.copy()
        source_path = subctx.create_anchor(ir_set, 'a')

        # Top-level json->tuple casts should produce an empty set on
        # null inputs, but error on missing fields or null
        # subelements, so filter out json nulls directly here to
        # distinguish those cases.
        if cardinality_mod != qlast.CardinalityModifier.Required:
            pathctx.register_set_in_scope(ir_set, ctx=subctx)

            check = qlast.FunctionCall(
                func=('__std__', 'json_typeof'), args=[source_path]
            )
            filtered = qlast.SelectQuery(
                result=source_path,
                where=qlast.BinOp(
                    left=check,
                    op='!=',
                    right=qlast.StringConstant(value='null'),
                )
            )
            filtered_ir = dispatch.compile(filtered, ctx=subctx)
            source_path = subctx.create_anchor(filtered_ir, 'a')

        # TODO: try using jsonb_to_record instead of a bunch of
        # json_get calls and see if that is faster.
        elements = []
        for new_el_name, new_st in new_stype.iter_subtypes(ctx.env.schema):
            val_e = qlast.FunctionCall(
                func=('__std__', 'json_get'),
                args=[
                    source_path,
                    qlast.StringConstant(value=new_el_name),
                ],
            )

            val = dispatch.compile(val_e, ctx=subctx)

            val = compile_cast(
                val, new_st,
                cardinality_mod=qlast.CardinalityModifier.Required,
                ctx=subctx, srcctx=srcctx)

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

        return setgen.new_tuple_set(
            elements,
            named=new_stype.is_named(ctx.env.schema),
            ctx=subctx,
        )
Beispiel #3
0
def compile_Tuple(expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Set:

    elements = []
    for i, el in enumerate(expr.elements):
        element = irast.TupleElement(name=str(i),
                                     val=setgen.ensure_set(dispatch.compile(
                                         el, ctx=ctx),
                                                           ctx=ctx))
        elements.append(element)

    return setgen.new_tuple_set(elements, named=False, ctx=ctx)
Beispiel #4
0
def compile_NamedTuple(expr: qlast.NamedTuple, *,
                       ctx: context.ContextLevel) -> irast.Set:

    elements = []
    for el in expr.elements:
        element = irast.TupleElement(name=el.name.name,
                                     val=setgen.ensure_set(dispatch.compile(
                                         el.val, ctx=ctx),
                                                           ctx=ctx))
        elements.append(element)

    return setgen.new_tuple_set(elements, named=True, ctx=ctx)
Beispiel #5
0
def compile_NamedTuple(expr: qlast.NamedTuple, *,
                       ctx: context.ContextLevel) -> irast.Set:

    names = set()
    elements = []
    for el in expr.elements:
        name = el.name.name
        if name in names:
            raise errors.QueryError(
                f"named tuple has duplicate field '{name}'",
                context=el.context)
        names.add(name)

        element = irast.TupleElement(
            name=name,
            val=dispatch.compile(el.val, ctx=ctx),
        )
        elements.append(element)

    return setgen.new_tuple_set(elements, named=True, ctx=ctx)
Beispiel #6
0
def _cast_json_to_tuple(ir_set: irast.Set, orig_stype: s_types.Type,
                        new_stype: s_types.Tuple, *,
                        srcctx: Optional[parsing.ParserContext],
                        ctx: context.ContextLevel) -> irast.Set:

    with ctx.new() as subctx:
        subctx.anchors = subctx.anchors.copy()
        source_alias = subctx.aliases.get('a')
        subctx.anchors[source_alias] = ir_set

        # TODO: try using jsonb_to_record instead of a bunch of
        # json_get calls and see if that is faster.
        elements = []
        for new_el_name, new_st in new_stype.iter_subtypes(ctx.env.schema):
            val_e = qlast.FunctionCall(
                func=('__std__', 'json_get'),
                args=[
                    qlast.Path(steps=[qlast.ObjectRef(name=source_alias)]),
                    qlast.StringConstant(value=new_el_name),
                ],
            )

            val = dispatch.compile(val_e, ctx=subctx)

            val = compile_cast(
                val,
                new_st,
                cardinality_mod=qlast.CardinalityModifier.Required,
                ctx=subctx,
                srcctx=srcctx)

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

        return setgen.new_tuple_set(
            elements,
            named=new_stype.is_named(ctx.env.schema),
            ctx=subctx,
        )
Beispiel #7
0
def _cast_tuple(ir_set: irast.Set, orig_stype: s_types.Type,
                new_stype: s_types.Type, *,
                srcctx: Optional[parsing.ParserContext],
                ctx: context.ContextLevel) -> irast.Set:

    assert isinstance(orig_stype, s_types.Tuple)

    # Make sure the source tuple expression is pinned in the scope,
    # so that we don't generate a cross-product of it by evaluating
    # the tuple indirections.
    pathctx.register_set_in_scope(ir_set, ctx=ctx)

    direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx)
    orig_subtypes = dict(orig_stype.iter_subtypes(ctx.env.schema))

    if direct_cast is not None:
        # Direct casting to non-tuple involves casting each tuple
        # element and also keeping the cast around the whole tuple.
        # This is to trigger the downstream logic of casting
        # objects (in elements of the tuple).
        elements = []
        for n in orig_subtypes:
            val = setgen.tuple_indirection_set(
                ir_set,
                source=orig_stype,
                ptr_name=n,
                ctx=ctx,
            )
            val_type = setgen.get_set_type(val, ctx=ctx)
            # Element cast
            val = compile_cast(val, new_stype, ctx=ctx, srcctx=srcctx)

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

        new_tuple = setgen.new_tuple_set(
            elements,
            named=orig_stype.is_named(ctx.env.schema),
            ctx=ctx,
        )

        return _cast_to_ir(new_tuple,
                           direct_cast,
                           orig_stype,
                           new_stype,
                           ctx=ctx)

    if not new_stype.is_tuple(ctx.env.schema):
        raise errors.QueryError(
            f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} '
            f'to {new_stype.get_displayname(ctx.env.schema)!r}',
            context=srcctx)

    assert isinstance(new_stype, s_types.Tuple)
    new_subtypes = list(new_stype.iter_subtypes(ctx.env.schema))
    if len(orig_subtypes) != len(new_subtypes):
        raise errors.QueryError(
            f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} '
            f'to {new_stype.get_displayname(ctx.env.schema)!r}: '
            f'the number of elements is not the same',
            context=srcctx)

    # For tuple-to-tuple casts we generate a new tuple
    # to simplify things on sqlgen side.
    elements = []
    for i, n in enumerate(orig_subtypes):
        val = setgen.tuple_indirection_set(
            ir_set,
            source=orig_stype,
            ptr_name=n,
            ctx=ctx,
        )
        val_type = setgen.get_set_type(val, ctx=ctx)
        new_el_name, new_st = new_subtypes[i]
        if val_type != new_st:
            # Element cast
            val = compile_cast(val, new_st, ctx=ctx, srcctx=srcctx)

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

    return setgen.new_tuple_set(
        elements,
        named=new_stype.is_named(ctx.env.schema),
        ctx=ctx,
    )