Пример #1
0
def _new_mapped_pointer_rvar(
        ir_ptr: irast.Pointer, *,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    ptrcls = ir_ptr.ptrcls
    ptr_rvar = dbobj.range_for_pointer(ir_ptr, env=ctx.env)
    # Set up references according to the link direction.
    if isinstance(ptrcls, s_links.Link):
        # XXX: fix this once Properties are Sources
        src_ptr_info = pg_types.get_pointer_storage_info(
            ptrcls.getptr(ctx.env.schema, 'source'), resolve_type=False,
            schema=ctx.env.schema)
        src_col = src_ptr_info.column_name
    else:
        src_col = 'source'

    source_ref = pgast.ColumnRef(name=[src_col], nullable=False)

    if isinstance(ptrcls, s_links.Link):
        # XXX: fix this once Properties are Sources
        tgt_ptr_info = pg_types.get_pointer_storage_info(
            ptrcls.getptr(ctx.env.schema, 'target'), resolve_type=False,
            schema=ctx.env.schema)
        tgt_col = tgt_ptr_info.column_name
    else:
        tgt_col = 'target'

    target_ref = pgast.ColumnRef(
        name=[tgt_col],
        nullable=not ptrcls.get_required(ctx.env.schema))

    if ir_ptr.direction == s_pointers.PointerDirection.Inbound:
        near_ref = target_ref
        far_ref = source_ref
    else:
        near_ref = source_ref
        far_ref = target_ref

    src_pid = ir_ptr.source.path_id
    tgt_pid = ir_ptr.target.path_id
    ptr_pid = tgt_pid.ptr_path()

    ptr_rvar.query.path_id = ptr_pid
    pathctx.put_rvar_path_bond(ptr_rvar, src_pid)
    pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='identity',
                                 var=near_ref, env=ctx.env)
    pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='value',
                                 var=near_ref, env=ctx.env)
    pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='value',
                                 var=far_ref, env=ctx.env)

    if tgt_pid.is_objtype_path():
        pathctx.put_rvar_path_bond(ptr_rvar, tgt_pid)
        pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='identity',
                                     var=far_ref, env=ctx.env)

    return ptr_rvar
Пример #2
0
def _new_mapped_pointer_rvar(
        ir_ptr: irast.Pointer, nullable: bool=False, *,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    ptrcls = ir_ptr.ptrcls
    ptr_rvar = dbobj.range_for_pointer(ir_ptr, env=ctx.env)
    ptr_rvar.nullable = nullable
    # Set up references according to the link direction.
    if isinstance(ptrcls, s_links.Link):
        # XXX: fix this once Properties are Sources
        src_ptr_info = pg_types.get_pointer_storage_info(
            ptrcls.getptr(ctx.env.schema, 'std::source'), resolve_type=False)
        src_col = src_ptr_info.column_name
    else:
        src_col = common.edgedb_name_to_pg_name('std::source')

    source_ref = dbobj.get_column(None, src_col)

    if isinstance(ptrcls, s_links.Link):
        # XXX: fix this once Properties are Sources
        tgt_ptr_info = pg_types.get_pointer_storage_info(
            ptrcls.getptr(ctx.env.schema, 'std::target'), resolve_type=False)
        tgt_col = tgt_ptr_info.column_name
    else:
        tgt_col = common.edgedb_name_to_pg_name('std::target')

    target_ref = dbobj.get_column(None, tgt_col)

    if ir_ptr.direction == s_pointers.PointerDirection.Inbound:
        near_ref = target_ref
        far_ref = source_ref
    else:
        near_ref = source_ref
        far_ref = target_ref

    ptr_rvar.query.path_id = ir_ptr.target.path_id.ptr_path()
    ptr_rvar.path_scope.add(ptr_rvar.query.path_id)

    src_pid = ir_ptr.source.path_id
    tgt_pid = ir_ptr.target.path_id
    ptr_rvar.path_scope.add(src_pid)

    pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='identity',
                                 var=near_ref, env=ctx.env)
    pathctx.put_rvar_path_output(ptr_rvar, src_pid, aspect='value',
                                 var=near_ref, env=ctx.env)
    pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='value',
                                 var=far_ref, env=ctx.env)

    if tgt_pid.is_objtype_path():
        ptr_rvar.path_scope.add(tgt_pid)
        pathctx.put_rvar_path_output(ptr_rvar, tgt_pid, aspect='identity',
                                     var=far_ref, env=ctx.env)

    return ptr_rvar
Пример #3
0
def new_root_rvar(
        ir_set: irast.Set, *,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    if not isinstance(ir_set.stype, s_objtypes.ObjectType):
        raise ValueError('cannot create root rvar for non-object path')

    set_rvar = dbobj.range_for_set(ir_set, env=ctx.env)
    pathctx.put_rvar_path_bond(set_rvar, ir_set.path_id)
    set_rvar.value_scope.add(ir_set.path_id)

    if ir_set.rptr and ir_set.rptr.is_inbound:
        ptrcls = ir_set.rptr.ptrcls
        ptr_info = pg_types.get_pointer_storage_info(
            ptrcls, resolve_type=False, link_bias=False,
            schema=ctx.env.schema)

        if ptr_info.table_type == 'ObjectType':
            # Inline link
            rref = pgast.ColumnRef(
                name=[ptr_info.column_name],
                nullable=not ptrcls.get_required(ctx.env.schema))
            pathctx.put_rvar_path_bond(
                set_rvar, ir_set.path_id.src_path())
            pathctx.put_rvar_path_output(
                set_rvar, ir_set.path_id.src_path(),
                aspect='identity', var=rref, env=ctx.env)

    return set_rvar
Пример #4
0
def semi_join(stmt: pgast.Query, ir_set: irast.Set,
              src_rvar: pgast.BaseRangeVar, *,
              ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    """Join an IR Set using semi-join."""
    rptr = ir_set.rptr
    ptrcls = rptr.ptrcls
    ptr_info = pg_types.get_pointer_storage_info(ptrcls,
                                                 resolve_type=False,
                                                 link_bias=False)
    is_inline_ref = ptr_info.table_type == 'ObjectType'

    # Target set range.
    set_rvar = new_root_rvar(ir_set, ctx=ctx)

    # Link range.
    map_rvar = new_pointer_rvar(rptr, src_rvar=src_rvar, ctx=ctx)

    # Target identity in the target range.
    if rptr.is_inbound and is_inline_ref:
        tgt_pid = ir_set.path_id.extend(ptrcls)
    else:
        tgt_pid = ir_set.path_id

    tgt_ref = pathctx.get_rvar_path_identity_var(set_rvar,
                                                 tgt_pid,
                                                 env=ctx.env)

    include_rvar(ctx.rel, map_rvar, path_id=ir_set.path_id.ptr_path(), ctx=ctx)

    pathctx.get_path_identity_output(ctx.rel, ir_set.path_id, env=ctx.env)

    cond = astutils.new_binop(tgt_ref, ctx.rel, 'IN')
    stmt.where_clause = astutils.extend_binop(stmt.where_clause, cond)

    return set_rvar
Пример #5
0
def new_pointer_rvar(
        ir_ptr: irast.Pointer, *, nullable: bool=False,
        link_bias: bool=False, src_rvar: pgast.BaseRangeVar,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:

    ptrcls = ir_ptr.ptrcls

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

    if ptr_info.table_type == 'ObjectType':
        # Inline link
        return _new_inline_pointer_rvar(
            ir_ptr, nullable=nullable, ptr_info=ptr_info,
            src_rvar=src_rvar, ctx=ctx)
    else:
        return _new_mapped_pointer_rvar(ir_ptr, nullable, ctx=ctx)
Пример #6
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()
                                       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}')

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

    ptrcls = path_id.rptr()

    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)

    result = pgast.ColumnRef(name=[ptr_info.column_name],
                             nullable=rel.nullable or not ptrcls.required)
    rel.path_outputs[path_id, aspect] = result
    return result
Пример #7
0
def _compile_set_in_singleton_mode(
        node: irast.Set, *, ctx: context.CompilerContextLevel) -> pgast.Base:
    if isinstance(node, irast.EmptySet):
        return pgast.NullConstant()
    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(ctx.env.schema):
                if source.rptr:
                    raise RuntimeError(
                        'unexpectedly long path in simple expr')

            ptr_stor_info = pg_types.get_pointer_storage_info(
                ptrcls, schema=ctx.env.schema, resolve_type=False)

            colref = pgast.ColumnRef(name=[ptr_stor_info.column_name])
        elif isinstance(node.stype, s_scalars.ScalarType):
            colref = pgast.ColumnRef(
                name=[
                    common.edgedb_name_to_pg_name(
                        node.stype.get_name(ctx.env.schema))
                ]
            )
        else:
            colref = pgast.ColumnRef(
                name=[
                    common.edgedb_name_to_pg_name(
                        node.stype.get_name(ctx.env.schema))
                ]
            )

        return colref
Пример #8
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)
Пример #9
0
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)
Пример #10
0
def get_path_var(rel: pgast.BaseRelation, path_id: irast.PathId, *,
                 aspect: str, env: context.Environment) -> pgast.OutputVar:
    """Return an OutputVar for a given *path_id* in a given *rel*."""
    if isinstance(rel, pgast.CommonTableExpr):
        rel = rel.query

    # Check if we already have a var, before remapping the path_id.
    # This is useful for serialized aspect disambiguation in tuples,
    # since process_set_as_tuple() records serialized vars with
    # original path_id.
    if (path_id, aspect) in rel.path_namespace:
        return rel.path_namespace[path_id, aspect]

    if rel.view_path_id_map:
        path_id = map_path_id(path_id, rel.view_path_id_map)

    if (path_id, aspect) in rel.path_namespace:
        return rel.path_namespace[path_id, aspect]

    ptrcls = path_id.rptr()
    if ptrcls is not None:
        ptr_info = pg_types.get_pointer_storage_info(ptrcls,
                                                     resolve_type=False,
                                                     link_bias=False)
        ptr_dir = path_id.rptr_dir()
        is_inbound = ptr_dir == s_pointers.PointerDirection.Inbound
        if is_inbound:
            src_path_id = path_id
        else:
            src_path_id = path_id.src_path()

    else:
        ptr_info = None
        src_path_id = None
        ptr_dir = None

    if astutils.is_set_op_query(rel):
        cb = functools.partial(get_path_output_or_null,
                               env=env,
                               path_id=path_id,
                               aspect=aspect)

        outputs = astutils.for_each_query_in_set(rel, cb)

        first = None
        optional = False
        all_null = True
        nullable = False

        for colref, is_null in outputs:
            if colref.nullable:
                nullable = True
            if first is None:
                first = colref
            if is_null:
                optional = True
            else:
                all_null = False

        if all_null:
            raise LookupError(f'cannot find refs for '
                              f'path {path_id} {aspect} in {rel}')

        # Path vars produced by UNION expressions can be "optional",
        # i.e the record is accepted as-is when such var is NULL.
        # This is necessary to correctly join heterogeneous UNIONs.
        var = dbobj.get_rvar_var(None,
                                 first,
                                 optional=optional,
                                 nullable=optional or nullable)
        put_path_var(rel, path_id, var, aspect=aspect, env=env)
        return var

    if ptrcls is None:
        if len(path_id) == 1:
            # This is an scalar set derived from an expression.
            src_path_id = path_id

    elif ptrcls.is_link_property():
        if ptr_info.table_type != 'link' and not is_inbound:
            # This is a link prop that is stored in source rel,
            # step back to link source rvar.
            src_path_id = path_id.src_path().src_path()

    elif ptr_info.table_type != 'ObjectType' and not is_inbound:
        # Ref is in the mapping rvar.
        src_path_id = path_id.ptr_path()

    rel_rvar = maybe_get_path_rvar(rel, path_id, aspect=aspect, env=env)

    if rel_rvar is None:
        alt_aspect = get_less_specific_aspect(path_id, aspect)
        if alt_aspect is not None:
            rel_rvar = maybe_get_path_rvar(rel,
                                           path_id,
                                           aspect=alt_aspect,
                                           env=env)
    else:
        alt_aspect = None

    if rel_rvar is None:
        if src_path_id.is_objtype_path():
            if aspect == 'identity':
                src_aspect = 'value'
            else:
                src_aspect = 'source'
        else:
            src_aspect = aspect

        if isinstance(src_path_id.rptr(), irutils.TupleIndirectionLink):
            rel_rvar = maybe_get_path_rvar(rel,
                                           src_path_id,
                                           aspect=src_aspect,
                                           env=env)

            if rel_rvar is None:
                rel_rvar = maybe_get_path_rvar(rel,
                                               src_path_id.src_path(),
                                               aspect=src_aspect,
                                               env=env)
        else:
            rel_rvar = maybe_get_path_rvar(rel,
                                           src_path_id,
                                           aspect=src_aspect,
                                           env=env)

        if src_aspect != 'source' and path_id != src_path_id:
            rel_rvar = maybe_get_path_rvar(rel,
                                           src_path_id,
                                           aspect='source',
                                           env=env)

    if rel_rvar is None and alt_aspect is not None:
        # There is no source range var for the requested aspect,
        # check if there is a cached var with less specificity.
        var = rel.path_namespace.get((path_id, alt_aspect))
        if var is not None:
            put_path_var(rel, path_id, var, aspect=aspect, env=env)
            return var

    if rel_rvar is None:
        raise LookupError(f'there is no range var for '
                          f'{src_path_id} {src_aspect} in {rel}')

    source_rel = rel_rvar.query

    drilldown_path_id = map_path_id(path_id, rel.view_path_id_map)

    if source_rel in env.root_rels and len(source_rel.path_scope) == 1:
        if not drilldown_path_id.is_objtype_path() and ptrcls is not None:
            outer_path_id = drilldown_path_id.src_path()
        else:
            outer_path_id = drilldown_path_id

        path_id_map = {outer_path_id: next(iter(source_rel.path_scope))}

        drilldown_path_id = map_path_id(drilldown_path_id, path_id_map)

    outvar = get_path_output(source_rel,
                             drilldown_path_id,
                             ptr_info=ptr_info,
                             aspect=aspect,
                             env=env)

    var = dbobj.get_rvar_var(rel_rvar, outvar)
    put_path_var(rel, path_id, var, aspect=aspect, env=env)

    if isinstance(var, pgast.TupleVar):
        for element in var.elements:
            put_path_var(rel,
                         element.path_id,
                         element.val,
                         aspect=aspect,
                         env=env)

    return var
Пример #11
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()
                                       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.target
        else:
            target = path_id.target

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

        val = typecomp.cast(pgast.Constant(val=None, nullable=True),
                            source_type=target,
                            target_type=target,
                            force=True,
                            env=env)

        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)

        result = pgast.ColumnRef(name=[ptr_info.column_name],
                                 nullable=not ptrcls.required)
    rel.path_outputs[path_id, aspect] = result
    return result
Пример #12
0
def range_for_ptrcls(
        ptrcls: s_links.Link, direction: s_pointers.PointerDirection, *,
        include_overlays: bool=True,
        env: context.Environment) -> pgast.BaseRangeVar:
    """"Return a Range subclass corresponding to a given ptr step.

    If `ptrcls` is a generic link, then a simple RangeVar is returned,
    otherwise the return value may potentially be a UNION of all tables
    corresponding to a set of specialized links computed from the given
    `ptrcls` taking source inheritance into account.
    """
    linkname = ptrcls.shortname
    endpoint = ptrcls.source

    tgt_col = pgtypes.get_pointer_storage_info(
        ptrcls, resolve_type=False, link_bias=True).column_name

    cols = [
        'std::source',
        tgt_col
    ]

    set_ops = []

    ptrclses = set()

    for source in {endpoint} | set(endpoint.descendants(env.schema)):
        # Sift through the descendants to see who has this link
        try:
            src_ptrcls = source.pointers[linkname].material_type()
        except KeyError:
            # This source has no such link, skip it
            continue
        else:
            if src_ptrcls in ptrclses:
                # Seen this link already
                continue
            ptrclses.add(src_ptrcls)

        table = table_from_ptrcls(src_ptrcls, env=env)

        qry = pgast.SelectStmt()
        qry.from_clause.append(table)
        qry.rptr_rvar = table

        # Make sure all property references are pulled up properly
        for colname in cols:
            selexpr = pgast.ColumnRef(
                name=[table.alias.aliasname, colname])
            qry.target_list.append(
                pgast.ResTarget(val=selexpr, name=colname))

        set_ops.append(('union', qry))

        overlays = env.rel_overlays.get(src_ptrcls.shortname)
        if overlays and include_overlays:
            for op, cte in overlays:
                rvar = pgast.RangeVar(
                    relation=cte,
                    alias=pgast.Alias(
                        aliasname=env.aliases.get(cte.name)
                    )
                )

                qry = pgast.SelectStmt(
                    target_list=[
                        pgast.ResTarget(
                            val=pgast.ColumnRef(
                                name=[col]
                            )
                        )
                        for col in cols
                    ],
                    from_clause=[rvar],
                )
                set_ops.append((op, qry))

    rvar = range_from_queryset(set_ops, ptrcls, env=env)
    return rvar