Exemplo n.º 1
0
def new_empty_rvar(ir_set: irast.EmptySet, *,
                   ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    nullref_alias = ctx.env.aliases.get('e')
    val = typecomp.cast(pgast.Constant(val=None, nullable=True),
                        source_type=ir_set.scls,
                        target_type=ir_set.scls,
                        force=True,
                        env=ctx.env)

    nullrel = pgast.SelectStmt(
        target_list=[pgast.ResTarget(val=val, name=nullref_alias)],
        nullable=True)
    rvar = dbobj.rvar_for_rel(nullrel, env=ctx.env)
    rvar.path_scope.add(ir_set.path_id)
    rvar.value_scope.add(ir_set.path_id)
    null_ref = pgast.ColumnRef(name=[nullref_alias], nullable=True)
    pathctx.put_rvar_path_output(rvar,
                                 ir_set.path_id,
                                 aspect='value',
                                 var=null_ref,
                                 env=ctx.env)
    if ir_set.path_id.is_objtype_path():
        pathctx.put_rvar_path_output(rvar,
                                     ir_set.path_id,
                                     aspect='identity',
                                     var=null_ref,
                                     env=ctx.env)
    return rvar
Exemplo n.º 2
0
def tuple_var_as_json_object(tvar, *, env):
    if not tvar.named:
        return pgast.FuncCall(name=('jsonb_build_array', ),
                              args=[
                                  serialize_expr(t.val, nested=True, env=env)
                                  for t in tvar.elements
                              ],
                              null_safe=True,
                              nullable=tvar.nullable)
    else:
        keyvals = []

        for element in tvar.elements:
            rptr = element.path_id.rptr()
            if rptr is None:
                name = element.path_id[-1].name.name
            else:
                name = rptr.shortname.name
                if rptr.is_link_property():
                    name = '@' + name
            keyvals.append(pgast.Constant(val=name))
            if isinstance(element.val, pgast.TupleVar):
                val = serialize_expr(element.val, env=env)
            else:
                val = element.val
            keyvals.append(val)

        return pgast.FuncCall(name=('jsonb_build_object', ),
                              args=keyvals,
                              null_safe=True,
                              nullable=tvar.nullable)
Exemplo n.º 3
0
def _compile_set_in_singleton_mode(
        node: irast.Set, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    if isinstance(node, irast.EmptySet):
        return pgast.Constant(value=None)
    elif node.expr is not None:
        return dispatch.compile(node.expr, ctx=ctx)
    else:
        if node.rptr:
            ptrcls = node.rptr.ptrcls
            source = node.rptr.source

            if not ptrcls.is_link_property():
                if source.rptr:
                    raise RuntimeError('unexpectedly long path in simple expr')

            colref = pgast.ColumnRef(
                name=[common.edgedb_name_to_pg_name(ptrcls.shortname)])
        elif isinstance(node.scls, s_scalars.ScalarType):
            colref = pgast.ColumnRef(
                name=[common.edgedb_name_to_pg_name(node.scls.name)])
        else:
            colref = pgast.ColumnRef(
                name=[common.edgedb_name_to_pg_name(node.scls.name)])

        return colref
Exemplo n.º 4
0
def compile_Constant(expr: irast.Base, *,
                     ctx: context.CompilerContextLevel) -> pgast.Base:
    result = pgast.Constant(val=expr.value)
    result = typecomp.cast(result,
                           source_type=expr.type,
                           target_type=expr.type,
                           force=True,
                           env=ctx.env)
    return result
Exemplo n.º 5
0
def compile_TypeRef(expr: irast.Base, *,
                    ctx: context.CompilerContextLevel) -> pgast.Base:
    data_backend = ctx.env.backend
    schema = ctx.env.schema

    if expr.subtypes:
        raise NotImplementedError()
    else:
        cls = schema.get(expr.maintype)
        objtype_id = data_backend.get_objtype_id(cls)
        result = pgast.TypeCast(arg=pgast.Constant(val=objtype_id),
                                type_name=pgast.TypeName(name=('uuid', )))

    return result
Exemplo n.º 6
0
def new_static_class_rvar(
        ir_set: irast.Set,
        *,
        lateral: bool = True,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    set_rvar = new_root_rvar(ir_set, ctx=ctx)
    clsname = pgast.Constant(val=ir_set.rptr.source.scls.material_type().name)
    nameref = dbobj.get_column(set_rvar,
                               common.edgedb_name_to_pg_name('schema::name'))
    condition = astutils.new_binop(nameref, clsname, op='=')
    substmt = pgast.SelectStmt()
    include_rvar(substmt, set_rvar, ir_set.path_id, aspect='value', ctx=ctx)
    substmt.where_clause = astutils.extend_binop(substmt.where_clause,
                                                 condition)
    return new_rel_rvar(ir_set, substmt, ctx=ctx)
Exemplo n.º 7
0
def get_path_output_or_null(
        rel: pgast.Query, path_id: irast.PathId, *,
        aspect: str, env: context.Environment) -> \
        typing.Tuple[pgast.OutputVar, bool]:
    try:
        ref = get_path_output(rel, path_id, aspect=aspect, env=env)
        is_null = False
    except LookupError:
        alias = env.aliases.get('null')
        restarget = pgast.ResTarget(name=alias, val=pgast.Constant(val=None))
        if hasattr(rel, 'returning_list'):
            rel.returning_list.append(restarget)
        else:
            rel.target_list.append(restarget)
        is_null = True
        ref = pgast.ColumnRef(name=[alias], nullable=True)

    return ref, is_null
Exemplo n.º 8
0
Arquivo: dml.py Projeto: virajs/edgedb
def process_link_values(ir_stmt,
                        ir_expr,
                        target_tab,
                        tab_cols,
                        col_data,
                        dml_rvar,
                        sources,
                        props_only,
                        target_is_scalar,
                        iterator_cte,
                        *,
                        ctx=context.CompilerContext) -> pgast.CommonTableExpr:
    """Unpack data from an update expression into a series of selects.

    :param ir_expr:
        IR of the INSERT/UPDATE body element.
    :param target_tab:
        The link table being updated.
    :param tab_cols:
        A sequence of columns in the table being updated.
    :param col_data:
        Expressions used to populate well-known columns of the link
        table such as std::source and std::__type__.
    :param sources:
        A list of relations which must be joined into the data query
        to resolve expressions in *col_data*.
    :param props_only:
        Whether this link update only touches link properties.
    :param target_is_scalar:
        Whether the link target is an ScalarType.
    :param iterator_cte:
        CTE representing the iterator range in the FOR clause of the
        EdgeQL DML statement.
    """
    with ctx.newscope() as newscope, newscope.newrel() as subrelctx:
        row_query = subrelctx.rel

        relctx.include_rvar(row_query, dml_rvar, ctx=subrelctx)
        subrelctx.path_scope[ir_stmt.subject.path_id] = row_query

        if iterator_cte is not None:
            iterator_rvar = dbobj.rvar_for_rel(iterator_cte,
                                               lateral=True,
                                               env=subrelctx.env)
            relctx.include_rvar(row_query,
                                iterator_rvar,
                                iterator_cte.query.path_id,
                                aspect='value',
                                ctx=subrelctx)

        with subrelctx.newscope() as sctx, sctx.subrel() as input_rel_ctx:
            input_rel = input_rel_ctx.rel
            if iterator_cte is not None:
                input_rel_ctx.path_scope[iterator_cte.query.path_id] = \
                    row_query
            input_rel_ctx.expr_exposed = False
            input_rel_ctx.shape_format = context.ShapeFormat.FLAT
            input_rel_ctx.volatility_ref = pathctx.get_path_identity_var(
                row_query, ir_stmt.subject.path_id, env=input_rel_ctx.env)
            dispatch.compile(ir_expr, ctx=input_rel_ctx)

    input_stmt = input_rel

    input_rvar = pgast.RangeSubselect(
        subquery=input_rel,
        lateral=True,
        alias=pgast.Alias(aliasname=ctx.env.aliases.get('val')))

    row = pgast.ImplicitRowExpr()

    source_data = {}

    if input_stmt.op is not None:
        # UNION
        input_stmt = input_stmt.rarg

    path_id = ir_expr.path_id

    output = pathctx.get_path_value_output(input_stmt, path_id, env=ctx.env)

    if isinstance(output, pgast.TupleVar):
        for element in output.elements:
            name = element.path_id.rptr_name()
            if name is None:
                name = element.path_id[-1].name
            colname = common.edgedb_name_to_pg_name(name)
            source_data.setdefault(colname,
                                   dbobj.get_column(input_rvar, element.name))
    else:
        if target_is_scalar:
            target_ref = pathctx.get_rvar_path_value_var(input_rvar,
                                                         path_id,
                                                         env=ctx.env)
        else:
            target_ref = pathctx.get_rvar_path_identity_var(input_rvar,
                                                            path_id,
                                                            env=ctx.env)

        source_data['std::target'] = target_ref

    if not target_is_scalar and 'std::target' not in source_data:
        target_ref = pathctx.get_rvar_path_identity_var(input_rvar,
                                                        path_id,
                                                        env=ctx.env)
        source_data['std::target'] = target_ref

    for col in tab_cols:
        expr = col_data.get(col)
        if expr is None:
            expr = source_data.get(col)

        if expr is None:
            if tab_cols[col]['column_default'] is not None:
                expr = pgast.LiteralExpr(expr=tab_cols[col]['column_default'])
            else:
                expr = pgast.Constant(val=None)

        row.args.append(expr)

    row_query.target_list = [
        pgast.ResTarget(val=pgast.Indirection(arg=pgast.TypeCast(
            arg=row, type_name=pgast.TypeName(name=target_tab)),
                                              indirection=[pgast.Star()]))
    ]

    row_query.from_clause += list(sources) + [input_rvar]

    link_rows = pgast.CommonTableExpr(query=row_query,
                                      name=ctx.env.aliases.get(hint='r'))

    return link_rows
Exemplo n.º 9
0
Arquivo: dml.py Projeto: virajs/edgedb
def process_link_update(
        ir_stmt: irast.MutatingStmt, ir_expr: irast.Base, props_only: bool,
        wrapper: pgast.Query, dml_cte: pgast.CommonTableExpr,
        iterator_cte: pgast.CommonTableExpr, *,
        ctx: context.CompilerContextLevel) -> typing.Optional[pgast.Query]:
    """Perform updates to a link relation as part of a DML statement.

    :param ir_stmt:
        IR of the statement.
    :param ir_expr:
        IR of the INSERT/UPDATE body element.
    :param props_only:
        Whether this link update only touches link properties.
    :param wrapper:
        Top-level SQL query.
    :param dml_cte:
        CTE representing the SQL INSERT or UPDATE to the main
        relation of the Object.
    :param iterator_cte:
        CTE representing the iterator range in the FOR clause of the
        EdgeQL DML statement.
    """
    toplevel = ctx.toplevel_stmt

    edgedb_ptr_tab = pgast.RangeVar(
        relation=pgast.Relation(schemaname='edgedb', name='pointer'),
        alias=pgast.Alias(aliasname=ctx.env.aliases.get(hint='ptr')))

    ltab_alias = edgedb_ptr_tab.alias.aliasname

    rptr = ir_expr.rptr
    ptrcls = rptr.ptrcls
    target_is_scalar = isinstance(ptrcls.target, s_scalars.ScalarType)

    path_id = rptr.source.path_id.extend(ptrcls, rptr.direction,
                                         rptr.target.scls)

    # The links in the dml class shape have been derived,
    # but we must use the correct specialized link class for the
    # base material type.
    mptrcls = ptrcls.material_type()

    # Lookup link class id by link name.
    lname_to_id = pgast.CommonTableExpr(query=pgast.SelectStmt(
        from_clause=[edgedb_ptr_tab],
        target_list=[
            pgast.ResTarget(val=pgast.ColumnRef(name=[ltab_alias, 'id']))
        ],
        where_clause=astutils.new_binop(
            lexpr=pgast.ColumnRef(name=[ltab_alias, 'name']),
            rexpr=pgast.Constant(val=mptrcls.name),
            op=ast.ops.EQ)),
                                        name=ctx.env.aliases.get(hint='lid'))

    lname_to_id_rvar = pgast.RangeVar(relation=lname_to_id)
    toplevel.ctes.append(lname_to_id)

    target_rvar = dbobj.range_for_ptrcls(mptrcls,
                                         '>',
                                         include_overlays=False,
                                         env=ctx.env)
    target_alias = target_rvar.alias.aliasname

    if target_is_scalar:
        target_tab_name = (target_rvar.relation.schemaname,
                           target_rvar.relation.name)
    else:
        target_tab_name = common.link_name_to_table_name(mptrcls.shortname,
                                                         catenate=False)

    tab_cols = \
        ctx.env.backend._type_mech.get_cached_table_columns(target_tab_name)

    assert tab_cols, "could not get cols for {!r}".format(target_tab_name)

    dml_cte_rvar = pgast.RangeVar(
        relation=dml_cte,
        alias=pgast.Alias(aliasname=ctx.env.aliases.get('m')))

    col_data = {
        'ptr_item_id':
        pgast.ColumnRef(name=[lname_to_id.name, 'id']),
        'std::source':
        pathctx.get_rvar_path_identity_var(dml_cte_rvar,
                                           ir_stmt.subject.path_id,
                                           env=ctx.env)
    }

    # Drop all previous link records for this source.
    delcte = pgast.CommonTableExpr(query=pgast.DeleteStmt(
        relation=target_rvar,
        where_clause=astutils.new_binop(
            lexpr=col_data['std::source'],
            op=ast.ops.EQ,
            rexpr=pgast.ColumnRef(name=[target_alias, 'std::source'])),
        using_clause=[dml_cte_rvar],
        returning_list=[
            pgast.ResTarget(val=pgast.ColumnRef(
                name=[target_alias, pgast.Star()]))
        ]),
                                   name=ctx.env.aliases.get(hint='d'))

    pathctx.put_path_value_rvar(delcte.query,
                                path_id.ptr_path(),
                                target_rvar,
                                env=ctx.env)

    # Record the effect of this removal in the relation overlay
    # context to ensure that the RETURNING clause potentially
    # referencing this link yields the expected results.
    overlays = ctx.env.rel_overlays[ptrcls.shortname]
    overlays.append(('except', delcte))
    toplevel.ctes.append(delcte)

    # Turn the IR of the expression on the right side of :=
    # into a subquery returning records for the link table.
    data_cte = process_link_values(ir_stmt,
                                   ir_expr,
                                   target_tab_name,
                                   tab_cols,
                                   col_data,
                                   dml_cte_rvar, [lname_to_id_rvar],
                                   props_only,
                                   target_is_scalar,
                                   iterator_cte,
                                   ctx=ctx)

    toplevel.ctes.append(data_cte)

    data_select = pgast.SelectStmt(
        target_list=[
            pgast.ResTarget(val=pgast.ColumnRef(
                name=[data_cte.name, pgast.Star()]))
        ],
        from_clause=[pgast.RangeVar(relation=data_cte)])

    # Inserting rows into the link table may produce cardinality
    # constraint violations, since the INSERT into the link table
    # is executed in the snapshot where the above DELETE from
    # the link table is not visible.  Hence, we need to use
    # the ON CONFLICT clause to resolve this.
    conflict_cols = ['std::source', 'std::target', 'ptr_item_id']
    conflict_inference = []
    conflict_exc_row = []

    for col in conflict_cols:
        conflict_inference.append(pgast.ColumnRef(name=[col]))
        conflict_exc_row.append(pgast.ColumnRef(name=['excluded', col]))

    conflict_data = pgast.SelectStmt(
        target_list=[
            pgast.ResTarget(val=pgast.ColumnRef(
                name=[data_cte.name, pgast.Star()]))
        ],
        from_clause=[pgast.RangeVar(relation=data_cte)],
        where_clause=astutils.new_binop(
            lexpr=pgast.ImplicitRowExpr(args=conflict_inference),
            rexpr=pgast.ImplicitRowExpr(args=conflict_exc_row),
            op='='))

    cols = [pgast.ColumnRef(name=[col]) for col in tab_cols]
    updcte = pgast.CommonTableExpr(
        name=ctx.env.aliases.get(hint='i'),
        query=pgast.InsertStmt(
            relation=target_rvar,
            select_stmt=data_select,
            cols=cols,
            on_conflict=pgast.OnConflictClause(
                action='update',
                infer=pgast.InferClause(index_elems=conflict_inference),
                target_list=[
                    pgast.MultiAssignRef(columns=cols, source=conflict_data)
                ]),
            returning_list=[
                pgast.ResTarget(val=pgast.ColumnRef(name=[pgast.Star()]))
            ]))

    pathctx.put_path_value_rvar(updcte.query,
                                path_id.ptr_path(),
                                target_rvar,
                                env=ctx.env)

    # Record the effect of this insertion in the relation overlay
    # context to ensure that the RETURNING clause potentially
    # referencing this link yields the expected results.
    overlays = ctx.env.rel_overlays[ptrcls.shortname]
    overlays.append(('union', updcte))

    toplevel.ctes.append(updcte)

    return data_cte
Exemplo n.º 10
0
Arquivo: dml.py Projeto: virajs/edgedb
def process_insert_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query,
                        insert_cte: pgast.CommonTableExpr,
                        insert_rvar: pgast.BaseRangeVar, *,
                        ctx: context.CompilerContextLevel) -> None:
    """Generate SQL DML CTEs from an InsertStmt IR.

    :param ir_stmt:
        IR of the statement.
    :param wrapper:
        Top-level SQL query.
    :param insert_cte:
        CTE representing the SQL INSERT to the main relation of the Object.
    """
    cols = [pgast.ColumnRef(name=['std::__type__'])]
    select = pgast.SelectStmt(target_list=[])
    values = select.target_list

    # The main INSERT query of this statement will always be
    # present to insert at least the std::id and std::__type__
    # links.
    insert_stmt = insert_cte.query

    insert_stmt.cols = cols
    insert_stmt.select_stmt = select

    if ir_stmt.parent_stmt is not None:
        iterator_set = ir_stmt.parent_stmt.iterator_stmt
    else:
        iterator_set = None

    if iterator_set is not None:
        with ctx.substmt() as ictx:
            ictx.path_scope = ictx.path_scope.new_child()
            ictx.path_scope[iterator_set.path_id] = ictx.rel
            clauses.compile_iterator_expr(ictx.rel, iterator_set, ctx=ictx)
            ictx.rel.path_id = iterator_set.path_id
            pathctx.put_path_bond(ictx.rel, iterator_set.path_id)
            iterator_cte = pgast.CommonTableExpr(
                query=ictx.rel, name=ctx.env.aliases.get('iter'))
            ictx.toplevel_stmt.ctes.append(iterator_cte)
        iterator_rvar = dbobj.rvar_for_rel(iterator_cte, env=ctx.env)
        relctx.include_rvar(select,
                            iterator_rvar,
                            path_id=ictx.rel.path_id,
                            ctx=ctx)
        iterator_id = pathctx.get_path_identity_var(select,
                                                    iterator_set.path_id,
                                                    env=ctx.env)
    else:
        iterator_cte = None
        iterator_id = None

    values.append(
        pgast.ResTarget(val=pgast.SelectStmt(
            target_list=[pgast.ResTarget(val=pgast.ColumnRef(name=['id']))],
            from_clause=[
                pgast.RangeVar(relation=pgast.Relation(name='objecttype',
                                                       schemaname='edgedb'))
            ],
            where_clause=astutils.new_binop(
                op=ast.ops.EQ,
                lexpr=pgast.ColumnRef(name=['name']),
                rexpr=pgast.Constant(val=ir_stmt.subject.scls.shortname)))))

    external_inserts = []
    tuple_elements = []
    parent_link_props = []

    with ctx.newrel() as subctx:
        subctx.rel = select
        subctx.rel_hierarchy[select] = insert_stmt

        subctx.expr_exposed = False
        subctx.shape_format = context.ShapeFormat.FLAT

        if iterator_cte is not None:
            subctx.path_scope = ctx.path_scope.new_child()
            subctx.path_scope[iterator_cte.query.path_id] = select

        # Process the Insert IR and separate links that go
        # into the main table from links that are inserted into
        # a separate link table.
        for shape_el in ir_stmt.subject.shape:
            rptr = shape_el.rptr
            ptrcls = rptr.ptrcls.material_type()

            if (ptrcls.is_link_property()
                    and rptr.source.path_id != ir_stmt.subject.path_id):
                parent_link_props.append(shape_el)
                continue

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

            props_only = False

            # First, process all local link inserts.
            if ptr_info.table_type == 'ObjectType':
                props_only = True
                field = pgast.ColumnRef(name=[ptr_info.column_name])
                cols.append(field)

                insvalue = insert_value_for_shape_element(insert_stmt,
                                                          wrapper,
                                                          ir_stmt,
                                                          shape_el,
                                                          iterator_id,
                                                          ptr_info=ptr_info,
                                                          ctx=subctx)

                tuple_el = astutils.tuple_element_for_shape_el(shape_el, field)
                tuple_elements.append(tuple_el)
                values.append(pgast.ResTarget(val=insvalue))

            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_inserts.append((shape_el, props_only))

        if iterator_cte is not None:
            cols.append(pgast.ColumnRef(name=['__edb_token']))

            values.append(pgast.ResTarget(val=iterator_id))

            pathctx.put_path_identity_var(insert_stmt,
                                          iterator_set.path_id,
                                          cols[-1],
                                          force=True,
                                          env=subctx.env)

            pathctx.put_path_bond(insert_stmt, iterator_set.path_id)

    toplevel = ctx.toplevel_stmt
    toplevel.ctes.append(insert_cte)

    # Process necessary updates to the link tables.
    for shape_el, props_only in external_inserts:
        process_link_update(ir_stmt,
                            shape_el,
                            props_only,
                            wrapper,
                            insert_cte,
                            iterator_cte,
                            ctx=ctx)

    if parent_link_props:
        prop_elements = []

        with ctx.newscope() as scopectx:
            scopectx.rel = wrapper

            for shape_el in parent_link_props:
                rptr = shape_el.rptr
                scopectx.path_scope[rptr.source.path_id] = wrapper
                pathctx.put_path_rvar_if_not_exists(wrapper,
                                                    rptr.source.path_id,
                                                    insert_rvar,
                                                    aspect='value',
                                                    env=scopectx.env)
                dispatch.compile(shape_el, ctx=scopectx)
                tuple_el = astutils.tuple_element_for_shape_el(shape_el, None)
                prop_elements.append(tuple_el)

        valtuple = pgast.TupleVar(elements=prop_elements, named=True)
        pathctx.put_path_value_var(wrapper,
                                   ir_stmt.subject.path_id,
                                   valtuple,
                                   force=True,
                                   env=ctx.env)
Exemplo n.º 11
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 isinstance(expr.op, ast.ops.TypeCheckOperator):
        result = pgast.FuncCall(name=('edgedb', 'issubclass'),
                                args=[left, right])

        if expr.op == ast.ops.IS_NOT:
            result = astutils.new_unop(ast.ops.NOT, result)

    else:
        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.º 12
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.º 13
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_types.Map):
        # When we compile maps we always cast keys to text,
        # hence we need to cast the index to text here.
        index_type = _infer_type(expr.index, ctx=ctx)
        index = typecomp.cast(index,
                              source_type=index_type,
                              target_type=ctx.env.schema.get('std::str'),
                              env=ctx.env)

        if isinstance(arg_type.element_type, s_types.Array):
            return typecomp.cast(astutils.new_binop(lexpr=subj,
                                                    op='->',
                                                    rexpr=index),
                                 source_type=ctx.env.schema.get('std::json'),
                                 target_type=arg_type.element_type,
                                 env=ctx.env)

        elif isinstance(arg_type.element_type, s_types.Map):
            return astutils.new_binop(lexpr=subj, op='->', rexpr=index)

        else:
            return typecomp.cast(astutils.new_binop(lexpr=subj,
                                                    op='->>',
                                                    rexpr=index),
                                 source_type=ctx.env.schema.get('std::str'),
                                 target_type=arg_type.element_type,
                                 env=ctx.env)

    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.º 14
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]))

        elif target_type.schema_name == 'map':
            if source_type.issubclass(json_t):
                # If the source type is json do nothing, since
                # maps are already encoded in json.
                return node

            # EdgeQL: <map<Tkey,Tval>>MAP<Vkey,Vval>
            # to SQL: SELECT jsonb_object_agg(
            #                    key::Vkey::Tkey::text,
            #                    value::Vval::Tval)
            #         FROM jsonb_each_text(MAP)

            key_cast = cast(
                cast(
                    cast(pgast.ColumnRef(name=['key']),
                         source_type=str_t,
                         target_type=source_type.key_type,
                         env=env),
                    source_type=source_type.key_type,
                    target_type=target_type.key_type,
                    env=env,
                ),
                source_type=target_type.key_type,
                target_type=str_t,
                env=env,
            )

            target_v_type = target_type.element_type

            val_cast = cast(cast(pgast.ColumnRef(name=['value']),
                                 source_type=str_t,
                                 target_type=source_type.element_type,
                                 env=env),
                            source_type=source_type.element_type,
                            target_type=target_v_type,
                            env=env)

            map_cast = pgast.SelectStmt(
                target_list=[
                    pgast.ResTarget(
                        val=pgast.FuncCall(name=('jsonb_object_agg', ),
                                           args=[key_cast, val_cast]))
                ],
                from_clause=[
                    pgast.RangeFunction(functions=[
                        pgast.FuncCall(name=('jsonb_each_text', ), args=[node])
                    ])
                ])

            return pgast.FuncCall(
                name=('coalesce', ),
                args=[
                    map_cast,
                    pgast.TypeCast(arg=pgast.Constant(val='{}'),
                                   type_name=pgast.TypeName(name=('jsonb', )))
                ])

    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}')