Exemplo n.º 1
0
def named_tuple_as_json_object(expr, *, stype, env):
    assert stype.is_tuple() and stype.named

    keyvals = []
    subtypes = stype.iter_subtypes()
    for el_idx, (el_name, el_type) in enumerate(subtypes):
        keyvals.append(pgast.StringConstant(val=el_name))

        type_sentinel = pgast.TypeCast(
            arg=pgast.NullConstant(),
            type_name=pgast.TypeName(
                name=pgtypes.pg_type_from_object(env.schema, el_type)))

        val = pgast.FuncCall(name=('edgedb', 'row_getattr_by_num'),
                             args=[
                                 expr,
                                 pgast.NumericConstant(val=str(el_idx + 1)),
                                 type_sentinel
                             ])

        if el_type.is_collection():
            val = coll_as_json_object(val, stype=el_type, env=env)

        keyvals.append(val)

    return pgast.FuncCall(name=('jsonb_build_object', ),
                          args=keyvals,
                          null_safe=True,
                          ser_safe=True,
                          nullable=expr.nullable)
Exemplo n.º 2
0
def insert_value_for_shape_element(
        insert_stmt: pgast.InsertStmt, wrapper: pgast.Query,
        ir_stmt: irast.MutatingStmt, shape_el: irast.Set,
        iterator_id: pgast.OutputVar, *, ptr_info: pg_types.PointerStorageInfo,
        ctx: context.CompilerContextLevel) -> pgast.OutputVar:

    rel = compile_insert_shape_element(insert_stmt,
                                       wrapper,
                                       ir_stmt,
                                       shape_el,
                                       iterator_id,
                                       ctx=ctx)

    insvalue = pathctx.get_path_value_var(rel, shape_el.path_id, env=ctx.env)

    if isinstance(insvalue, pgast.TupleVar):
        for element in insvalue.elements:
            name = element.path_id.rptr_name()
            if name == 'std::target':
                insvalue = pathctx.get_path_value_var(rel,
                                                      element.path_id,
                                                      env=ctx.env)
                break
        else:
            raise RuntimeError('could not find std::target in '
                               'insert computable')
    insvalue = pgast.TypeCast(arg=insvalue,
                              type_name=typecomp.type_node(
                                  ptr_info.column_type))

    return insvalue
Exemplo n.º 3
0
def compile_TypeCast(
        expr: irast.TypeCast, *,
        ctx: context.CompilerContextLevel) -> pgast.Base:

    pg_expr = dispatch.compile(expr.expr, ctx=ctx)

    if expr.sql_cast:
        # Use explicit SQL cast.

        to_type = irutils.typeref_to_type(ctx.env.schema, expr.to_type)
        pg_type = pg_types.pg_type_from_object(ctx.env.schema, to_type)
        return pgast.TypeCast(
            arg=pg_expr,
            type_name=pgast.TypeName(
                name=pg_type
            )
        )

    elif expr.sql_function or expr.sql_expr:
        # Cast implemented as a function.

        if expr.sql_expr:
            func_name = common.get_backend_cast_name(
                expr.cast_name, aspect='function')
        else:
            func_name = (expr.sql_function,)

        return pgast.FuncCall(
            name=func_name,
            args=[pg_expr],
        )

    else:
        raise RuntimeError('cast not supported')
Exemplo n.º 4
0
def unnamed_tuple_as_json_object(expr, *, stype, env):
    assert stype.is_tuple() and not stype.named

    vals = []
    subtypes = stype.get_subtypes()
    for el_idx, el_type in enumerate(subtypes):
        type_sentinel = pgast.TypeCast(
            arg=pgast.NullConstant(),
            type_name=pgast.TypeName(
                name=pgtypes.pg_type_from_object(env.schema, el_type)))

        val = pgast.FuncCall(name=('edgedb', 'row_getattr_by_num'),
                             args=[
                                 expr,
                                 pgast.NumericConstant(val=str(el_idx + 1)),
                                 type_sentinel
                             ])

        if el_type.is_collection():
            val = coll_as_json_object(val, stype=el_type, env=env)

        vals.append(val)

    return pgast.FuncCall(name=(
        'edgedb',
        'row_to_jsonb_array',
    ),
                          args=[expr],
                          null_safe=True,
                          ser_safe=True,
                          nullable=expr.nullable)
Exemplo n.º 5
0
def compile_BooleanConstant(
        expr: irast.BooleanConstant, *,
        ctx: context.CompilerContextLevel) -> pgast.Base:

    return pgast.TypeCast(
        arg=pgast.BooleanConstant(val=expr.value),
        type_name=pgast.TypeName(
            name=pg_types.pg_type_from_object(
                ctx.env.schema, expr.stype)
        )
    )
Exemplo n.º 6
0
def compile_FunctionCall(
        expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base:

    if expr.typemod is ql_ft.TypeModifier.SET_OF:
        raise RuntimeError(
            'set returning functions are not supported in simple expressions')

    args = [dispatch.compile(a, ctx=ctx) for a in expr.args]

    if expr.has_empty_variadic:
        var = pgast.TypeCast(
            arg=pgast.ArrayExpr(elements=[]),
            type_name=pgast.TypeName(
                name=pg_types.pg_type_from_object(
                    ctx.env.schema, expr.variadic_param_type)
            )
        )

        args.append(pgast.VariadicArgument(expr=var))

    if expr.func_sql_function:
        name = (expr.func_sql_function,)
    else:
        name = common.schema_name_to_pg_name(expr.func_shortname)

    result = pgast.FuncCall(name=name, args=args)

    if expr.force_return_cast:
        # The underlying function has a return value type
        # different from that of the EdgeQL function declaration,
        # so we need to make an explicit cast here.
        result = pgast.TypeCast(
            arg=result,
            type_name=pgast.TypeName(
                name=pg_types.pg_type_from_object(
                    ctx.env.schema, expr.stype)
            )
        )

    return result
Exemplo n.º 7
0
def compile_SliceIndirection(
        expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    # Handle Expr[Index], where Expr may be std::str, array<T> or
    # std::json. For strings we translate this into substr calls.
    # Arrays use the native slice syntax. JSON is handled by a
    # combination of unnesting aggregation and array slicing.
    with ctx.new() as subctx:
        subctx.expr_exposed = False
        subj = dispatch.compile(expr.expr, ctx=subctx)
        if expr.start is None:
            start = pgast.NullConstant()
        else:
            start = dispatch.compile(expr.start, ctx=subctx)
        if expr.stop is None:
            stop = pgast.NullConstant()
        else:
            stop = dispatch.compile(expr.stop, ctx=subctx)

    # any integer indexes must be upcast into int to fit the helper
    # function signature
    start = pgast.TypeCast(
        arg=start,
        type_name=pgast.TypeName(
            name=('int',)
        )
    )
    stop = pgast.TypeCast(
        arg=stop,
        type_name=pgast.TypeName(
            name=('int',)
        )
    )

    result = pgast.FuncCall(
        name=('edgedb', '_slice'),
        args=[subj, start, stop]
    )

    return result
Exemplo n.º 8
0
def compile_Array(
        expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    elements = [dispatch.compile(e, ctx=ctx) for e in expr.elements]
    array = astutils.safe_array_expr(elements)

    if irutils.is_empty_array_expr(expr):
        return pgast.TypeCast(
            arg=array,
            type_name=pgast.TypeName(
                name=pg_types.pg_type_from_object(
                    ctx.env.schema, expr.stype)
            )
        )
    else:
        return array
Exemplo n.º 9
0
def compile_IndexIndirection(expr: irast.Base, *,
                             ctx: context.CompilerContextLevel) -> pgast.Base:
    # Handle Expr[Index], where Expr may be std::str or array<T>.
    # For strings we translate this into substr calls, whereas
    # for arrays the native slice syntax is used.
    is_string = False
    arg_type = _infer_type(expr.expr, ctx=ctx)

    with ctx.new() as subctx:
        subctx.expr_exposed = False
        subj = dispatch.compile(expr.expr, ctx=subctx)
        index = dispatch.compile(expr.index, ctx=subctx)

    if isinstance(arg_type, s_scalars.ScalarType):
        b = arg_type.get_topmost_concrete_base()
        is_string = b.name == 'std::str'

    one = pgast.Constant(val=1)
    zero = pgast.Constant(val=0)

    when_cond = astutils.new_binop(lexpr=index, rexpr=zero, op=ast.ops.LT)

    index_plus_one = astutils.new_binop(lexpr=index, op=ast.ops.ADD, rexpr=one)

    if is_string:
        upper_bound = pgast.FuncCall(name=('char_length', ), args=[subj])
    else:
        upper_bound = pgast.FuncCall(name=('array_upper', ), args=[subj, one])

    neg_off = astutils.new_binop(lexpr=upper_bound,
                                 rexpr=index_plus_one,
                                 op=ast.ops.ADD)

    when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off)

    index = pgast.CaseExpr(args=[when_expr], defresult=index_plus_one)

    if is_string:
        index = pgast.TypeCast(arg=index,
                               type_name=pgast.TypeName(name=('int', )))
        result = pgast.FuncCall(name=('substr', ), args=[subj, index, one])
    else:
        indirection = pgast.Indices(ridx=index)
        result = pgast.Indirection(arg=subj, indirection=[indirection])

    return result
Exemplo n.º 10
0
def compile_IndexIndirection(
        expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    # Handle Expr[Index], where Expr may be std::str, array<T> or
    # std::json. For strings we translate this into substr calls.
    # Arrays use the native index access. JSON is handled by using the
    # `->` accessor. Additionally, in all of the above cases a
    # boundary-check is performed on the index and an exception is
    # potentially raised.

    # line, column and filename are captured here to be used with the
    # error message
    srcctx = pgast.StringConstant(
        val=irutils.get_source_context_as_json(expr.index,
                                               errors.InvalidValueError))

    with ctx.new() as subctx:
        subctx.expr_exposed = False
        subj = dispatch.compile(expr.expr, ctx=subctx)
        index = dispatch.compile(expr.index, ctx=subctx)

    # If the index is some integer, cast it into int, because there's
    # no backend function that handles indexes larger than int.
    index_t = expr.index.stype
    int_t = ctx.env.schema.get('std::anyint')
    if index_t.issubclass(ctx.env.schema, int_t):
        index = pgast.TypeCast(
            arg=index,
            type_name=pgast.TypeName(
                name=('int',)
            )
        )

    result = pgast.FuncCall(
        name=('edgedb', '_index'),
        args=[subj, index, srcctx]
    )

    return result
Exemplo n.º 11
0
def compile_Parameter(
        expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    if expr.name.isdecimal():
        index = int(expr.name) + 1
        result = pgast.ParamRef(number=index)
    else:
        if ctx.env.use_named_params:
            result = pgast.NamedParamRef(name=expr.name)
        else:
            if expr.name in ctx.argmap:
                index = ctx.argmap[expr.name]
            else:
                index = len(ctx.argmap) + 1
                ctx.argmap[expr.name] = index

            result = pgast.ParamRef(number=index)

    return pgast.TypeCast(
        arg=result,
        type_name=pgast.TypeName(
            name=pg_types.pg_type_from_object(
                ctx.env.schema, expr.stype)
        )
    )
Exemplo n.º 12
0
def cast(node: pgast.Base,
         *,
         source_type: s_obj.Object,
         target_type: s_obj.Object,
         force: bool = False,
         env: context.Environment) -> pgast.Base:

    if source_type.name == target_type.name and not force:
        return node

    schema = env.schema
    real_t = schema.get('std::anyreal')
    int_t = schema.get('std::anyint')
    json_t = schema.get('std::json')
    str_t = schema.get('std::str')
    datetime_t = schema.get('std::datetime')
    bool_t = schema.get('std::bool')

    if isinstance(target_type, s_types.Collection):
        if target_type.schema_name == 'array':

            if source_type.issubclass(json_t):
                # If we are casting a jsonb array to array, we do the
                # following transformation:
                # EdgeQL: <array<T>>MAP_VALUE
                # SQL:
                #      SELECT array_agg(j::T)
                #      FROM jsonb_array_elements(MAP_VALUE) AS j

                inner_cast = cast(pgast.ColumnRef(name=['j']),
                                  source_type=source_type,
                                  target_type=target_type.element_type,
                                  env=env)

                return pgast.SelectStmt(
                    target_list=[
                        pgast.ResTarget(val=pgast.FuncCall(
                            name=('array_agg', ), args=[inner_cast]))
                    ],
                    from_clause=[
                        pgast.RangeFunction(functions=[
                            pgast.FuncCall(name=('jsonb_array_elements', ),
                                           args=[node])
                        ],
                                            alias=pgast.Alias(aliasname='j'))
                    ])
            else:
                # EdgeQL: <array<int64>>['1', '2']
                # to SQL: ARRAY['1', '2']::int[]

                elem_pgtype = pg_types.pg_type_from_object(
                    schema, target_type.element_type, topbase=True)

                return pgast.TypeCast(arg=node,
                                      type_name=pgast.TypeName(
                                          name=elem_pgtype, array_bounds=[-1]))

    else:
        # `target_type` is not a collection.
        if (source_type.issubclass(datetime_t)
                and target_type.issubclass(str_t)):
            # Normalize datetime to text conversion to have the same
            # format as one would get by serializing to JSON.
            #
            # EdgeQL: <text><datetime>'2010-10-10';
            # To SQL: trim(to_json('2010-01-01'::timestamptz)::text, '"')
            return pgast.FuncCall(
                name=('trim', ),
                args=[
                    pgast.TypeCast(arg=pgast.FuncCall(name=('to_json', ),
                                                      args=[node]),
                                   type_name=pgast.TypeName(name=('text', ))),
                    pgast.Constant(val='"')
                ])

        elif source_type.issubclass(bool_t) and target_type.issubclass(int_t):
            # PostgreSQL 9.6 doesn't allow to cast 'boolean' to any integer
            # other than int32:
            #      SELECT 'true'::boolean::bigint;
            #      ERROR:  cannot cast type boolean to bigint
            # So we transform EdgeQL: <int64>BOOL
            # to SQL: BOOL::int::<targetint>
            return pgast.TypeCast(
                arg=pgast.TypeCast(arg=node,
                                   type_name=pgast.TypeName(name=('int', ))),
                type_name=pgast.TypeName(
                    name=pg_types.pg_type_from_scalar(schema, target_type)))

        elif source_type.issubclass(int_t) and target_type.issubclass(bool_t):
            # PostgreSQL 9.6 doesn't allow to cast any integer other
            # than int32 to 'boolean':
            #      SELECT 1::bigint::boolean;
            #      ERROR:  cannot cast type bigint to boolea
            # So we transform EdgeQL: <boolean>INT
            # to SQL: (INT != 0)
            return astutils.new_binop(node,
                                      pgast.Constant(val=0),
                                      op=ast.ops.NE)

        elif source_type.issubclass(json_t):
            if (target_type.issubclass(real_t)
                    or target_type.issubclass(bool_t)):
                # Simply cast to text and the to the target type.
                return cast(cast(node,
                                 source_type=source_type,
                                 target_type=str_t,
                                 env=env),
                            source_type=str_t,
                            target_type=target_type,
                            env=env)

            elif target_type.issubclass(str_t):
                # It's not possible to cast jsonb string to text directly,
                # so we do a trick:
                # EdgeQL: <str>JSONB_VAL
                # SQL: array_to_json(ARRAY[JSONB_VAL])->>0

                return astutils.new_binop(pgast.FuncCall(
                    name=('array_to_json', ),
                    args=[pgast.ArrayExpr(elements=[node])]),
                                          pgast.Constant(val=0),
                                          op='->>')

            elif target_type.issubclass(json_t):
                return pgast.TypeCast(
                    arg=node, type_name=pgast.TypeName(name=('jsonb', )))

        else:
            const_type = pg_types.pg_type_from_object(schema,
                                                      target_type,
                                                      topbase=True)

            return pgast.TypeCast(arg=node,
                                  type_name=pgast.TypeName(name=const_type))

    raise RuntimeError(
        f'could not cast {source_type.name} to {target_type.name}')
Exemplo n.º 13
0
def process_update_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query,
                        update_cte: pgast.CommonTableExpr,
                        range_cte: pgast.CommonTableExpr, *,
                        ctx: context.CompilerContextLevel):
    """Generate SQL DML CTEs from an UpdateStmt IR.

    :param ir_stmt:
        IR of the statement.
    :param wrapper:
        Top-level SQL query.
    :param update_cte:
        CTE representing the SQL UPDATE to the main relation of the Object.
    :param range_cte:
        CTE representing the range affected by the statement.
    """
    update_stmt = update_cte.query

    external_updates = []

    toplevel = ctx.toplevel_stmt
    toplevel.ctes.append(range_cte)
    toplevel.ctes.append(update_cte)

    with ctx.newscope() as subctx:
        # It is necessary to process the expressions in
        # the UpdateStmt shape body in the context of the
        # UPDATE statement so that references to the current
        # values of the updated object are resolved correctly.
        subctx.path_scope[ir_stmt.subject.path_id] = update_stmt
        subctx.rel = update_stmt
        subctx.expr_exposed = False
        subctx.shape_format = context.ShapeFormat.FLAT

        for shape_el in ir_stmt.subject.shape:
            with subctx.newscope() as scopectx:
                ptrcls = shape_el.rptr.ptrcls
                updvalue = shape_el.expr

                ptr_info = pg_types.get_pointer_storage_info(
                    ptrcls,
                    schema=scopectx.env.schema,
                    resolve_type=True,
                    link_bias=False)

                # First, process all internal link updates
                if ptr_info.table_type == 'ObjectType':
                    updvalue = pgast.TypeCast(
                        arg=dispatch.compile(updvalue, ctx=scopectx),
                        type_name=typecomp.type_node(ptr_info.column_type))

                    update_stmt.targets.append(
                        pgast.UpdateTarget(name=ptr_info.column_name,
                                           val=updvalue))

            props_only = is_props_only_update(shape_el)

            ptr_info = pg_types.get_pointer_storage_info(ptrcls,
                                                         resolve_type=False,
                                                         link_bias=True)

            if ptr_info and ptr_info.table_type == 'link':
                external_updates.append((shape_el, props_only))

    if not update_stmt.targets:
        # No updates directly to the set target table,
        # so convert the UPDATE statement into a SELECT.
        update_cte.query = pgast.SelectStmt(
            ctes=update_stmt.ctes,
            target_list=update_stmt.returning_list,
            from_clause=[update_stmt.relation] + update_stmt.from_clause,
            where_clause=update_stmt.where_clause,
            path_namespace=update_stmt.path_namespace,
            path_outputs=update_stmt.path_outputs,
            path_scope=update_stmt.path_scope,
            path_rvar_map=update_stmt.path_rvar_map.copy(),
            view_path_id_map=update_stmt.view_path_id_map.copy(),
            ptr_join_map=update_stmt.ptr_join_map.copy(),
        )

    # Process necessary updates to the link tables.
    for expr, props_only in external_updates:
        if props_only:
            process_linkprop_update(ir_stmt,
                                    expr,
                                    wrapper,
                                    update_cte,
                                    ctx=ctx)
        else:
            process_link_update(ir_stmt,
                                expr,
                                False,
                                wrapper,
                                update_cte,
                                None,
                                ctx=ctx)
Exemplo n.º 14
0
def compile_OperatorCall(
        expr: irast.OperatorCall, *,
        ctx: context.CompilerContextLevel) -> pgast.Expr:

    if expr.typemod is ql_ft.TypeModifier.SET_OF:
        raise RuntimeError(
            f'set returning operator {expr.func_shortname!r} is not supported '
            f'in simple expressions')

    args = [dispatch.compile(a, ctx=ctx) for a in expr.args]
    if expr.operator_kind is ql_ft.OperatorKind.INFIX:
        lexpr, rexpr = args
    elif expr.operator_kind is ql_ft.OperatorKind.PREFIX:
        rexpr = args[0]
        lexpr = None
    elif expr.operator_kind is ql_ft.OperatorKind.POSTFIX:
        lexpr = args[0]
        rexpr = None
    else:
        raise RuntimeError(f'unexpected operator kind: {expr.operator_kind!r}')

    if expr.sql_operator:
        sql_oper = expr.sql_operator[0]
        if len(expr.sql_operator) > 1:
            # Explicit operand types given in FROM SQL OPERATOR
            if lexpr is not None:
                lexpr = pgast.TypeCast(
                    arg=lexpr,
                    type_name=pgast.TypeName(
                        name=(expr.sql_operator[1],)
                    )
                )

            if rexpr is not None:
                rexpr = pgast.TypeCast(
                    arg=rexpr,
                    type_name=pgast.TypeName(
                        name=(expr.sql_operator[2],)
                    )
                )
    else:
        sql_oper = common.get_backend_operator_name(expr.func_shortname)[1]

    result = pgast.Expr(
        kind=pgast.ExprKind.OP,
        name=sql_oper,
        lexpr=lexpr,
        rexpr=rexpr,
    )

    if expr.force_return_cast:
        # The underlying operator has a return value type
        # different from that of the EdgeQL operator declaration,
        # so we need to make an explicit cast here.
        result = pgast.TypeCast(
            arg=result,
            type_name=pgast.TypeName(
                name=pg_types.pg_type_from_object(
                    ctx.env.schema, expr.stype)
            )
        )

    return result
Exemplo n.º 15
0
def compile_BinOp(expr: irast.Base, *,
                  ctx: context.CompilerContextLevel) -> pgast.Base:

    with ctx.new() as newctx:
        newctx.expr_exposed = False
        op = expr.op
        is_bool_op = op in {ast.ops.AND, ast.ops.OR}
        left = dispatch.compile(expr.left, ctx=newctx)
        right = dispatch.compile(expr.right, ctx=newctx)

    if not isinstance(expr.left, irast.EmptySet):
        left_type = _infer_type(expr.left, ctx=ctx)
    else:
        left_type = None

    if not isinstance(expr.right, irast.EmptySet):
        right_type = _infer_type(expr.right, ctx=ctx)
    else:
        right_type = None

    if (not isinstance(expr.left, irast.EmptySet)
            and not isinstance(expr.right, irast.EmptySet)):
        left_pg_type = pg_types.pg_type_from_object(ctx.env.schema, left_type,
                                                    True)

        right_pg_type = pg_types.pg_type_from_object(ctx.env.schema,
                                                     right_type, True)

        if (left_pg_type in {('text', ), ('varchar', )}
                and right_pg_type in {('text', ),
                                      ('varchar', )} and op == ast.ops.ADD):
            op = '||'

    if isinstance(left_type, s_types.Tuple):
        left = _tuple_to_row_expr(expr.left, ctx=newctx)
        left_count = len(left.args)
    else:
        left_count = 0

    if isinstance(right_type, s_types.Tuple):
        right = _tuple_to_row_expr(expr.right, ctx=newctx)
        right_count = len(right.args)
    else:
        right_count = 0

    if left_count != right_count:
        # Postgres does not allow comparing rows with
        # unequal number of entries, but we want to allow
        # this.  Fortunately, we know that such comparison is
        # always False.
        result = pgast.Constant(val=False)
    else:
        if is_bool_op:
            # Transform logical operators to force
            # the correct behaviour with respect to NULLs.
            # See the OrFilterFunction comment for details.
            if ctx.clause == 'where':
                if expr.op == ast.ops.OR:
                    result = pgast.FuncCall(name=('edgedb', '_or'),
                                            args=[left, right])
                else:
                    # For the purposes of the WHERE clause,
                    # AND operator works correctly, as
                    # it will either return NULL or FALSE,
                    # which both will disqualify the row.
                    result = astutils.new_binop(left, right, op=op)
            else:
                # For expressions outside WHERE, we
                # always want the result to be NULL
                # if either operand is NULL.
                bitop = '&' if expr.op == ast.ops.AND else '|'
                bitcond = astutils.new_binop(
                    lexpr=pgast.TypeCast(
                        arg=left, type_name=pgast.TypeName(name=('int', ))),
                    rexpr=pgast.TypeCast(
                        arg=right, type_name=pgast.TypeName(name=('int', ))),
                    op=bitop)
                bitcond = pgast.TypeCast(
                    arg=bitcond, type_name=pgast.TypeName(name=('bool', )))
                result = bitcond
        else:
            result = astutils.new_binop(left, right, op=op)

    return result
Exemplo n.º 16
0
def compile_SliceIndirection(expr: irast.Base, *,
                             ctx: context.CompilerContextLevel) -> pgast.Base:
    # Handle Expr[Start:End], where Expr may be std::str or array<T>.
    # For strings we translate this into substr calls, whereas
    # for arrays the native slice syntax is used.
    with ctx.new() as subctx:
        subctx.expr_exposed = False
        subj = dispatch.compile(expr.expr, ctx=subctx)
        start = dispatch.compile(expr.start, ctx=subctx)
        stop = dispatch.compile(expr.stop, ctx=subctx)

    one = pgast.Constant(val=1)
    zero = pgast.Constant(val=0)

    is_string = False
    arg_type = _infer_type(expr.expr, ctx=ctx)

    if isinstance(arg_type, s_scalars.ScalarType):
        b = arg_type.get_topmost_concrete_base()
        is_string = b.name == 'std::str'

    if is_string:
        upper_bound = pgast.FuncCall(name=('char_length', ), args=[subj])
    else:
        upper_bound = pgast.FuncCall(name=('array_upper', ), args=[subj, one])

    if astutils.is_null_const(start):
        lower = one
    else:
        lower = start

        when_cond = astutils.new_binop(lexpr=lower, rexpr=zero, op=ast.ops.LT)
        lower_plus_one = astutils.new_binop(lexpr=lower,
                                            rexpr=one,
                                            op=ast.ops.ADD)

        neg_off = astutils.new_binop(lexpr=upper_bound,
                                     rexpr=lower_plus_one,
                                     op=ast.ops.ADD)

        when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off)
        lower = pgast.CaseExpr(args=[when_expr], defresult=lower_plus_one)

    if astutils.is_null_const(stop):
        upper = upper_bound
    else:
        upper = stop

        when_cond = astutils.new_binop(lexpr=upper, rexpr=zero, op=ast.ops.LT)

        neg_off = astutils.new_binop(lexpr=upper_bound,
                                     rexpr=upper,
                                     op=ast.ops.ADD)

        when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off)
        upper = pgast.CaseExpr(args=[when_expr], defresult=upper)

    if is_string:
        lower = pgast.TypeCast(arg=lower,
                               type_name=pgast.TypeName(name=('int', )))

        args = [subj, lower]

        if upper is not upper_bound:
            for_length = astutils.new_binop(lexpr=upper,
                                            op=ast.ops.SUB,
                                            rexpr=lower)
            for_length = astutils.new_binop(lexpr=for_length,
                                            op=ast.ops.ADD,
                                            rexpr=one)

            for_length = pgast.TypeCast(
                arg=for_length, type_name=pgast.TypeName(name=('int', )))
            args.append(for_length)

        result = pgast.FuncCall(name=('substr', ), args=args)

    else:
        indirection = pgast.Indices(lidx=lower, ridx=upper)
        result = pgast.Indirection(arg=subj, indirection=[indirection])

    return result
Exemplo n.º 17
0
def _get_rel_path_output(rel: pgast.BaseRelation,
                         path_id: irast.PathId,
                         *,
                         aspect: str,
                         ptr_info: typing.Optional[
                             pg_types.PointerStorageInfo] = None,
                         env: context.Environment) -> pgast.OutputVar:

    if path_id.is_objtype_path():
        if aspect == 'identity':
            aspect = 'value'

        if aspect != 'value':
            raise LookupError(
                f'invalid request for non-scalar path {path_id} {aspect}')

        if (path_id == rel.path_id
                or (rel.path_id.is_type_indirection_path(env.schema)
                    and path_id == rel.path_id.src_path())):
            path_id = irutils.get_id_path_id(path_id, schema=env.schema)
    else:
        if aspect == 'identity':
            raise LookupError(
                f'invalid request for scalar path {path_id} {aspect}')

        elif aspect == 'serialized':
            aspect = 'value'

    var = rel.path_outputs.get((path_id, aspect))
    if var is not None:
        return var

    ptrcls = path_id.rptr()
    rptr_dir = path_id.rptr_dir()

    if (rptr_dir is not None
            and rptr_dir != s_pointers.PointerDirection.Outbound):
        raise LookupError(
            f'{path_id} is an inbound pointer and cannot be resolved '
            f'on a base relation')

    if isinstance(rel, pgast.NullRelation):
        if ptrcls is not None:
            target = ptrcls.get_target(env.schema)
        else:
            target = path_id.target

        if ptr_info is not None:
            name = ptr_info.column_name
        else:
            name = env.aliases.get('v')

        val = pgast.TypeCast(
            arg=pgast.NullConstant(),
            type_name=pgast.TypeName(
                name=pg_types.pg_type_from_object(env.schema, target)))

        rel.target_list.append(pgast.ResTarget(name=name, val=val))
        result = pgast.ColumnRef(name=[name], nullable=True)
    else:
        if ptrcls is None:
            raise ValueError(
                f'could not resolve trailing pointer class for {path_id}')

        ptr_info = pg_types.get_pointer_storage_info(ptrcls,
                                                     resolve_type=False,
                                                     link_bias=False,
                                                     schema=env.schema)

        result = pgast.ColumnRef(name=[ptr_info.column_name],
                                 nullable=not ptrcls.get_required(env.schema))
    rel.path_outputs[path_id, aspect] = result
    return result