Exemplo n.º 1
0
def get_rvar_path_var(rvar: pgast.PathRangeVar, path_id: irast.PathId,
                      aspect: str, *,
                      env: context.Environment) -> pgast.OutputVar:
    """Return ColumnRef for a given *path_id* in a given *range var*."""

    if (path_id, aspect) in rvar.query.path_outputs:
        outvar = rvar.query.path_outputs[path_id, aspect]
    elif is_relation_rvar(rvar):
        if ((rptr := path_id.rptr()) is not None and rvar.typeref is not None
                and rvar.query.path_id != path_id
                and (not rvar.query.path_id.is_type_intersection_path()
                     or rvar.query.path_id.src_path() != path_id)):
            actual_rptr = irtyputils.find_actual_ptrref(
                rvar.typeref,
                rptr,
            )
            if actual_rptr is not None:
                ptr_si = pg_types.get_ptrref_storage_info(actual_rptr)
            else:
                ptr_si = None
        else:
            ptr_si = None

        outvar = _get_rel_path_output(rvar.query,
                                      path_id,
                                      ptr_info=ptr_si,
                                      aspect=aspect,
                                      env=env)
Exemplo n.º 2
0
def get_scope_stmt(path_id: irast.PathId, *,
                   ctx: context.CompilerContextLevel) -> pgast.Query:
    stmt = ctx.path_scope.get(path_id)
    if stmt is None and path_id.is_ptr_path():
        stmt = ctx.path_scope.get(path_id.tgt_path())
    if stmt is None:
        raise LookupError(f'cannot find scope statement for {path_id}')
    return stmt
Exemplo n.º 3
0
def maybe_get_scope_stmt(
    path_id: irast.PathId,
    *,
    ctx: context.CompilerContextLevel,
) -> Optional[pgast.SelectStmt]:
    stmt = ctx.path_scope.get(path_id)
    if stmt is None and path_id.is_ptr_path():
        stmt = ctx.path_scope.get(path_id.tgt_path())
    return stmt
Exemplo n.º 4
0
def get_path_output_alias(path_id: irast.PathId, aspect: str, *,
                          env: context.Environment) -> str:
    rptr = path_id.rptr()
    if rptr is not None:
        alias_base = rptr.shortname.name
    elif path_id.is_collection_path():
        alias_base = path_id.target.collection
    else:
        _, _, alias_base = path_id.target_name_hint.rpartition('::')

    return env.aliases.get(f'{alias_base}_{aspect}')
Exemplo n.º 5
0
def get_less_specific_aspect(path_id: irast.PathId, aspect: str):
    if path_id.is_objtype_path():
        mapping = OBJECT_ASPECT_SPECIFICITY_MAP
    else:
        mapping = PRIMITIVE_ASPECT_SPECIFICITY_MAP

    return mapping.get(PathAspect(aspect))
Exemplo n.º 6
0
def serialize_expr_to_json(
        expr: pgast.BaseExpr, *,
        path_id: irast.PathId,
        nested: bool=False,
        env: context.Environment) -> pgast.BaseExpr:

    val: pgast.BaseExpr

    if isinstance(expr, pgast.TupleVar):
        val = tuple_var_as_json_object(expr, path_id=path_id, env=env)

    elif isinstance(expr, (pgast.RowExpr, pgast.ImplicitRowExpr)):
        val = pgast.FuncCall(
            name=_get_json_func('build_array', env=env),
            args=expr.args, null_safe=True, ser_safe=True,)

    elif path_id.is_collection_path() and not expr.ser_safe:
        val = coll_as_json_object(expr, styperef=path_id.target, env=env)

    elif not nested:
        val = pgast.FuncCall(
            name=_get_json_func('to', env=env),
            args=[expr], null_safe=True, ser_safe=True)

    else:
        val = expr

    return val
Exemplo n.º 7
0
def range_for_typeref(typeref: irast.TypeRef,
                      path_id: irast.PathId,
                      *,
                      include_overlays: bool = True,
                      include_descendants: bool = True,
                      dml_source: Optional[irast.MutatingStmt] = None,
                      common_parent: bool = False,
                      ctx: context.CompilerContextLevel) -> pgast.PathRangeVar:

    if typeref.common_parent is not None and common_parent:
        rvar = range_for_material_objtype(
            typeref.common_parent,
            path_id,
            include_overlays=include_overlays,
            include_descendants=include_descendants,
            dml_source=dml_source,
            ctx=ctx,
        )

    elif typeref.union:
        # Union object types are represented as a UNION of selects
        # from their children, which is, for most purposes, equivalent
        # to SELECTing from a parent table.
        set_ops = []

        for child in typeref.union:
            c_rvar = range_for_typeref(
                child,
                path_id=path_id,
                include_overlays=include_overlays,
                include_descendants=not typeref.union_is_concrete,
                dml_source=dml_source,
                ctx=ctx,
            )

            qry = pgast.SelectStmt(from_clause=[c_rvar], )

            pathctx.put_path_value_rvar(qry, path_id, c_rvar, env=ctx.env)
            if path_id.is_objtype_path():
                pathctx.put_path_source_rvar(qry, path_id, c_rvar, env=ctx.env)

            pathctx.put_path_bond(qry, path_id)

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

        rvar = range_from_queryset(set_ops, typeref.name_hint, ctx=ctx)

    else:
        rvar = range_for_material_objtype(
            typeref,
            path_id,
            include_overlays=include_overlays,
            include_descendants=include_descendants,
            dml_source=dml_source,
            ctx=ctx,
        )

    rvar.query.path_id = path_id

    return rvar
Exemplo n.º 8
0
def _find_rvar_in_intersection_by_typeref(
    path_id: irast.PathId,
    component_rvars: Sequence[pgast.PathRangeVar],
) -> pgast.PathRangeVar:

    assert component_rvars

    pid_rptr = path_id.rptr()
    if pid_rptr is not None:
        if pid_rptr.material_ptr is not None:
            pid_rptr = pid_rptr.material_ptr
        tref = pid_rptr.out_source
    else:
        tref = path_id.target

    for component_rvar in component_rvars:
        if (component_rvar.typeref is not None
                and irtyputils.type_contains(tref, component_rvar.typeref)):
            rel_rvar = component_rvar
            break
    else:
        raise AssertionError(
            f'no rvar in intersection matches path id {path_id}')

    return rel_rvar
Exemplo n.º 9
0
def extend_path_id(
    path_id: irast.PathId,
    *,
    ptrcls: s_pointers.PointerLike,
    direction: s_pointers.PointerDirection = (
        s_pointers.PointerDirection.Outbound),
    ns: AbstractSet[str] = frozenset(),
    ctx: context.ContextLevel,
) -> irast.PathId:
    """A wrapper over :meth:`ir.pathid.PathId.extend` that also ensures
       the cardinality of *ptrcls* is known at the end of compilation.
    """

    ptrref = irtyputils.ptrref_from_ptrcls(
        schema=ctx.env.schema,
        ptrcls=ptrcls,
        direction=direction,
        cache=ctx.env.ptr_ref_cache,
        typeref_cache=ctx.env.type_ref_cache,
    )
    stmtctx.ensure_ptrref_cardinality(ptrcls, ptrref, ctx=ctx)

    return path_id.extend(ptrref=ptrref,
                          direction=direction,
                          ns=ns,
                          schema=ctx.env.schema)
Exemplo n.º 10
0
def _find_rel_rvar(
    rel: pgast.Query, path_id: irast.PathId, src_path_id: irast.PathId, *,
    aspect: str, env: context.Environment,
) -> Tuple[str, Optional[pgast.PathRangeVar], Optional[pgast.BaseExpr]]:
    """Rummage around rel looking for an appropriate rvar for path_id.

    Somewhat unfortunately, some checks to find the actual path var
    (in a particular tuple case) need to occur in the middle of the
    rvar rel search, so we can also find the actual path var in passing.
    """
    src_aspect = aspect
    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():
            src_aspect = 'source'
        else:
            src_aspect = aspect

        if src_path_id.is_tuple_path():
            if (var := _find_in_output_tuple(rel, path_id, aspect, env=env)):
                return src_aspect, None, var

            rel_rvar = maybe_get_path_rvar(
                rel, src_path_id, aspect=src_aspect, env=env)

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

        if (rel_rvar is None
                and src_aspect != 'source' and path_id != src_path_id):
            rel_rvar = maybe_get_path_rvar(
                rel, src_path_id, aspect='source', env=env)
Exemplo n.º 11
0
def map_path_id(path_id: irast.PathId,
                path_id_map: Dict[irast.PathId, irast.PathId]) -> irast.PathId:
    for outer_id, inner_id in path_id_map.items():
        new_path_id = path_id.replace_prefix(outer_id, inner_id)
        if new_path_id != path_id:
            path_id = new_path_id
            break

    return path_id
Exemplo n.º 12
0
def get_tuple_indirection_path_id(tuple_path_id: irast.PathId,
                                  element_name: str,
                                  element_type: s_types.Type, *,
                                  ctx: context.ContextLevel) -> irast.PathId:
    return tuple_path_id.extend(
        ptrcls=irast.TupleIndirectionLink(element_name),
        direction=s_pointers.PointerDirection.Outbound,
        target=element_type,
        schema=ctx.env.schema)
Exemplo n.º 13
0
def _find_in_output_tuple(
        rel: pgast.Query,
        path_id: irast.PathId,
        aspect: str,
        env: context.Environment) -> Optional[pgast.BaseExpr]:
    """Try indirecting a source tuple already present as an output.

    Normally tuple indirections are handled by
    process_set_as_tuple_indirection, but UNIONing an explicit tuple with a
    tuple coming from a base relation (like `(Foo.bar UNION (1,2)).0`)
    can lead to us looking for a tuple path in relations that only have
    the actual full tuple.
    (See test_edgeql_coalesce_tuple_{08,09}).

    We handle this by checking whether some prefix of the tuple path
    is present in the path_outputs.

    This is sufficient because the relevant cases are all caused by
    set ops, and the "fixup" done in set op cases ensures that the
    tuple will be already present.
    """

    steps = []
    src_path_id = path_id.src_path()
    ptrref = path_id.rptr()
    while (
        src_path_id
        and src_path_id.is_tuple_path()
        and isinstance(ptrref, irast.TupleIndirectionPointerRef)
    ):
        steps.append((ptrref.shortname.name, src_path_id))

        if (
            (var := rel.path_namespace.get((src_path_id, aspect)))
            and not isinstance(var, pgast.TupleVarBase)
        ):
            for name, src in reversed(steps):
                var = astutils.tuple_getattr(var, src.target, name)
            put_path_var(rel, path_id, var, aspect=aspect, env=env)
            return var

        ptrref = src_path_id.rptr()
        src_path_id = src_path_id.src_path()
Exemplo n.º 14
0
def map_path_id(path_id: irast.PathId,
                path_id_map: Dict[irast.PathId, irast.PathId]) -> irast.PathId:

    sorted_map = sorted(path_id_map.items(),
                        key=lambda kv: len(kv[0]),
                        reverse=True)

    for outer_id, inner_id in sorted_map:
        new_path_id = path_id.replace_prefix(outer_id, inner_id)
        if new_path_id != path_id:
            path_id = new_path_id
            break

    return path_id
Exemplo n.º 15
0
def get_less_specific_aspect(
    path_id: irast.PathId,
    aspect: str,
) -> Optional[str]:
    if path_id.is_objtype_path():
        mapping = OBJECT_ASPECT_SPECIFICITY_MAP
    else:
        mapping = PRIMITIVE_ASPECT_SPECIFICITY_MAP

    less_specific_aspect = mapping.get(PathAspect(aspect))
    if less_specific_aspect is not None:
        return str(less_specific_aspect)
    else:
        return None
Exemplo n.º 16
0
def get_type_indirection_path_id(path_id: irast.PathId,
                                 target_type: s_types.Type, *, optional: bool,
                                 ancestral: bool,
                                 cardinality: qltypes.Cardinality,
                                 ctx: context.ContextLevel) -> irast.PathId:
    return path_id.extend(ptrcls=irast.TypeIndirectionLink(
        path_id.target,
        target_type,
        optional=optional,
        ancestral=ancestral,
        cardinality=cardinality),
                          direction=s_pointers.PointerDirection.Outbound,
                          target=target_type,
                          schema=ctx.env.schema)
Exemplo n.º 17
0
def process_view(
    *,
    stype: s_objtypes.ObjectType,
    path_id: irast.PathId,
    elements: List[qlast.ShapeElement],
    view_rptr: Optional[context.ViewRPtr] = None,
    view_name: Optional[sn.QualName] = None,
    is_insert: bool = False,
    is_update: bool = False,
    is_delete: bool = False,
    parser_context: Optional[pctx.ParserContext],
    ctx: context.ContextLevel,
) -> s_objtypes.ObjectType:

    cache_key = (stype, is_insert, is_update, is_delete, tuple(elements))
    view_scls = ctx.shape_type_cache.get(cache_key)
    if view_scls is not None:
        return view_scls

    with ctx.newscope(fenced=True) as scopectx:
        scopectx.path_scope.is_temporary = True
        view_path_id_ns = None
        new_path_id = path_id
        if ctx.expr_exposed or is_insert or is_update:
            view_path_id_ns = ctx.aliases.get('tmpns')
            scopectx.path_id_namespace |= {view_path_id_ns}
            scopectx.path_scope.add_namespaces({view_path_id_ns})
            new_path_id = path_id.merge_namespace({view_path_id_ns})

        scopectx.path_scope.attach_path(new_path_id, context=parser_context)
        if ctx.path_log is not None:
            ctx.path_log.append(path_id)

        view_scls = _process_view(
            stype=stype,
            path_id=new_path_id,
            elements=elements,
            view_rptr=view_rptr,
            view_name=view_name,
            is_insert=is_insert,
            is_update=is_update,
            is_delete=is_delete,
            path_id_namespace=view_path_id_ns,
            parser_context=parser_context,
            ctx=scopectx,
        )

    ctx.shape_type_cache[cache_key] = view_scls

    return view_scls
Exemplo n.º 18
0
def new_external_rvar(
    *,
    rel_name: Tuple[str, ...],
    path_id: irast.PathId,
    outputs: Mapping[Tuple[irast.PathId, Tuple[str, ...]], str],
) -> pgast.RelRangeVar:
    """Construct a ``RangeVar`` instance given a relation name and a path id.

    Given an optionally-qualified relation name *rel_name* and a *path_id*,
    return a ``RangeVar`` instance over the specified relation that is
    then assumed to represent the *path_id* binding.

    This is useful in situations where it is necessary to "prime" the compiler
    with a list of external relations that exist in a larger SQL expression
    that _this_ expression is being embedded into.

    The *outputs* mapping optionally specifies a set of outputs in the
    resulting range var as a ``(path_id, tuple-of-aspects): attribute name``
    mapping.
    """
    if len(rel_name) == 1:
        table_name = rel_name[0]
        schema_name = None
    elif len(rel_name) == 2:
        schema_name, table_name = rel_name
    else:
        raise AssertionError(f'unexpected rvar name: {rel_name}')

    rel = pgast.Relation(
        name=table_name,
        schemaname=schema_name,
        path_id=path_id,
    )

    alias = pgast.Alias(aliasname=table_name)

    if not path_id.is_ptr_path():
        rvar = pgast.RelRangeVar(
            relation=rel, typeref=path_id.target, alias=alias)
    else:
        rvar = pgast.RelRangeVar(
            relation=rel, alias=alias)

    for (output_pid, output_aspects), colname in outputs.items():
        var = pgast.ColumnRef(name=[colname])
        for aspect in output_aspects:
            rel.path_outputs[output_pid, aspect] = var

    return rvar
Exemplo n.º 19
0
def range_for_typeref(typeref: irast.TypeRef,
                      path_id: irast.PathId,
                      *,
                      include_overlays: bool = True,
                      common_parent: bool = False,
                      env: context.Environment) -> pgast.BaseRangeVar:
    from . import pathctx  # XXX: fix cycle

    if not typeref.children:
        rvar = range_for_material_objtype(typeref,
                                          path_id,
                                          include_overlays=include_overlays,
                                          env=env)
    elif common_parent:
        rvar = range_for_material_objtype(typeref.common_parent,
                                          path_id,
                                          include_overlays=include_overlays,
                                          env=env)
    else:
        # Union object types are represented as a UNION of selects
        # from their children, which is, for most purposes, equivalent
        # to SELECTing from a parent table.
        set_ops = []

        for child in typeref.children:
            c_rvar = range_for_typeref(child,
                                       path_id=path_id,
                                       include_overlays=include_overlays,
                                       env=env)

            qry = pgast.SelectStmt(from_clause=[c_rvar], )

            pathctx.put_path_value_rvar(qry, path_id, c_rvar, env=env)
            if path_id.is_objtype_path():
                pathctx.put_path_source_rvar(qry, path_id, c_rvar, env=env)

            pathctx.put_path_bond(qry, path_id)

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

        rvar = range_from_queryset(set_ops, typeref.name_hint, env=env)

    rvar.query.path_id = path_id

    return rvar
Exemplo n.º 20
0
def extend_path_id(path_id: irast.PathId,
                   *,
                   ptrcls,
                   direction=None,
                   target=None,
                   ns=None,
                   ctx: context.ContextLevel) -> irast.PathId:

    result = path_id.extend(ptrcls=ptrcls,
                            direction=direction,
                            target=target,
                            ns=ns,
                            schema=ctx.env.schema)

    ptrref = result.rptr()
    stmtctx.ensure_ptrref_cardinality(ptrcls, ptrref, ctx=ctx)

    return result
Exemplo n.º 21
0
def get_tuple_indirection_path_id(
        tuple_path_id: irast.PathId, element_name: str,
        element_type: s_types.Type, *,
        ctx: context.ContextLevel) -> irast.PathId:

    ptrcls = irast.TupleIndirectionLink(
        irtyputils.ir_typeref_to_type(ctx.env.schema, tuple_path_id.target),
        element_type,
        element_name=element_name,
    )

    ptrref = irtyputils.ptrref_from_ptrcls(
        schema=ctx.env.schema,
        ptrcls=ptrcls,
        # FIXME: caching disabled here since it breaks tests
        # cache=ctx.env.ptr_ref_cache,
        # typeref_cache=ctx.env.type_ref_cache,
    )

    return tuple_path_id.extend(schema=ctx.env.schema, ptrref=ptrref)
Exemplo n.º 22
0
def include_rvar(stmt: pgast.SelectStmt,
                 rvar: pgast.PathRangeVar,
                 path_id: irast.PathId,
                 *,
                 overwrite_path_rvar: bool = False,
                 pull_namespace: bool = True,
                 aspects: Optional[Iterable[str]] = None,
                 ctx: context.CompilerContextLevel) -> pgast.PathRangeVar:
    """Ensure that *rvar* is visible in *stmt* as a value/source aspect.

    :param stmt:
        The statement to include *rel* in.

    :param rvar:
        The range var node to join.

    :param join_type:
        JOIN type to use when including *rel*.

    :param aspect:
        The reference aspect of the range var.

    :param ctx:
        Compiler context.
    """
    if aspects is None:
        if path_id.is_objtype_path():
            aspects = ('source', 'value')
        else:
            aspects = ('value', )

    return include_specific_rvar(stmt,
                                 rvar=rvar,
                                 path_id=path_id,
                                 overwrite_path_rvar=overwrite_path_rvar,
                                 pull_namespace=pull_namespace,
                                 aspects=aspects,
                                 ctx=ctx)
Exemplo n.º 23
0
def _find_rvar_in_intersection(
    path_id: irast.PathId,
    component_rvars: Sequence[pgast.PathRangeVar],
) -> pgast.PathRangeVar:

    assert component_rvars

    pid_rptr = path_id.rptr()
    if pid_rptr is not None:
        if pid_rptr.material_ptr is not None:
            pid_rptr = pid_rptr.material_ptr
        tref = pid_rptr.out_source
    else:
        tref = path_id.target

    for component_rvar in component_rvars:
        assert component_rvar.typeref is not None
        if irtyputils.type_contains(tref, component_rvar.typeref):
            rel_rvar = component_rvar
            break
    else:
        rel_rvar = component_rvars[0]

    return rel_rvar
Exemplo n.º 24
0
def get_path_var(rel: pgast.Query, path_id: irast.PathId, *, aspect: str,
                 env: context.Environment) -> pgast.BaseExpr:
    """Return a value expression 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]

    ptrref = path_id.rptr()
    is_type_indirection = path_id.is_type_indirection_path()
    if ptrref is not None and not is_type_indirection:
        ptr_info = pg_types.get_ptrref_storage_info(ptrref,
                                                    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()
            src_rptr = src_path_id.rptr()
            if (irtyputils.is_id_ptrref(ptrref)
                    and (src_rptr is None
                         or not irtyputils.is_inbound_ptrref(src_rptr))):
                # When there is a reference to the id property of
                # an object which is linked to by a link stored
                # inline, we want to route the reference to the
                # inline attribute.  For example,
                # Foo.__type__.id gets resolved to the Foo.__type__
                # column.  This can only be done if Foo is visible
                # in scope, and Foo.__type__ is not a computable.
                pid = src_path_id
                while pid.is_type_indirection_path():
                    # Skip type indirection step(s).
                    src_pid = pid.src_path()
                    if src_pid is not None:
                        src_rptr = src_pid.rptr()
                        pid = src_pid
                    else:
                        break

                src_src_is_visible = env.ptrref_source_visibility.get(src_rptr)

                if (src_rptr is not None
                        and not irtyputils.is_computable_ptrref(src_rptr)
                        and src_src_is_visible):
                    src_ptr_info = pg_types.get_ptrref_storage_info(
                        src_rptr, resolve_type=False, link_bias=False)
                    if src_ptr_info.table_type == 'ObjectType':
                        src_path_id = src_path_id.src_path()
                        ptr_info = src_ptr_info

    else:
        ptr_info = None
        src_path_id = None
        ptr_dir = None

    var: typing.Optional[pgast.BaseExpr]

    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 = astutils.strip_output_var(first,
                                        optional=optional,
                                        nullable=optional or nullable)
        put_path_var(rel, path_id, var, aspect=aspect, env=env)
        return var

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

    elif ptrref.parent_ptr is not None:
        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 (is_type_indirection
          or (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():
            src_aspect = 'source'
        else:
            src_aspect = aspect

        if src_path_id.is_tuple_path():
            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 (rel_rvar is None and 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 ptrref 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 = astutils.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_if_not_exists(rel,
                                       element.path_id,
                                       element.val,
                                       aspect=aspect,
                                       env=env)

    return var
Exemplo n.º 25
0
def new_primitive_rvar(
    ir_set: irast.Set,
    *,
    path_id: irast.PathId,
    ctx: context.CompilerContextLevel,
) -> pgast.PathRangeVar:
    if not ir_set.path_id.is_objtype_path():
        raise ValueError('cannot create root rvar for non-object path')

    typeref = ir_set.typeref
    dml_source = irutils.get_nearest_dml_stmt(ir_set)
    set_rvar = range_for_typeref(typeref,
                                 path_id,
                                 dml_source=dml_source,
                                 ctx=ctx)
    pathctx.put_rvar_path_bond(set_rvar, path_id)

    if ir_set.rptr is not None:
        ptr_ref_map: Dict[uuid.UUID, irast.BasePointerRef] = {}
        p: irast.BasePointerRef

        rptrref = ir_set.rptr.ptrref
        if isinstance(rptrref, irast.TypeIntersectionPointerRef):
            if rptrref.rptr_specialization:
                for p in rptrref.rptr_specialization:
                    ptr_ref_map[p.dir_target.id] = p

            src_set = ir_set.rptr.source
            if src_set.rptr is not None:
                src_rptrref = src_set.rptr.ptrref
                if src_rptrref.union_components:
                    for p in src_rptrref.union_components:
                        ptr_ref_map[p.dir_target.id] = p
                else:
                    ptr_ref_map[src_rptrref.dir_target.id] = src_rptrref
                rptrref = src_rptrref
            else:
                ptr_ref_map[rptrref.dir_target.id] = rptrref
        else:
            if rptrref.union_components:
                for p in rptrref.union_components:
                    ptr_ref_map[p.dir_target.id] = p
            else:
                ptr_ref_map[rptrref.dir_target.id] = rptrref

        if (set_rvar.typeref is not None
                and (narrow_rptrref := ptr_ref_map.get(set_rvar.typeref.id))):
            rptrref = narrow_rptrref

        ptr_info = pg_types.get_ptrref_storage_info(rptrref,
                                                    resolve_type=False,
                                                    link_bias=False)

        if ptr_info.table_type == 'ObjectType' and rptrref.is_inbound:
            # Inline link
            prefix_path_id = path_id.src_path()
            assert prefix_path_id is not None, 'expected a path'
            rref = pgast.ColumnRef(name=[ptr_info.column_name],
                                   nullable=not rptrref.required)
            pathctx.put_rvar_path_bond(set_rvar, prefix_path_id)
            pathctx.put_rvar_path_output(set_rvar,
                                         prefix_path_id,
                                         aspect='identity',
                                         var=rref,
                                         env=ctx.env)

            if astutils.is_set_op_query(set_rvar.query):
                assert isinstance(set_rvar.query, pgast.SelectStmt)

                def _pull_col(comp_qry: pgast.Query) -> None:
                    rvar = pathctx.get_path_rvar(comp_qry,
                                                 path_id,
                                                 aspect='source',
                                                 env=ctx.env)
                    typeref = rvar.typeref
                    assert typeref is not None
                    comp_ptrref = ptr_ref_map[typeref.id]
                    comp_pi = pg_types.get_ptrref_storage_info(
                        comp_ptrref, resolve_type=False, link_bias=False)

                    comp_qry.target_list.append(
                        pgast.ResTarget(
                            val=pgast.ColumnRef(name=[comp_pi.column_name]),
                            name=ptr_info.column_name,
                        ))

                astutils.for_each_query_in_set(
                    set_rvar.query,
                    _pull_col,
                )
            elif isinstance(set_rvar, pgast.RangeSubselect):
                rvar_path_var = pathctx.maybe_get_path_rvar(
                    set_rvar.query,
                    path_id=path_id,
                    aspect='identity',
                    env=ctx.env,
                )

                if isinstance(rvar_path_var, pgast.IntersectionRangeVar):
                    for comp_rvar in rvar_path_var.component_rvars:
                        if comp_rvar.typeref is None:
                            continue
                        comp_ptrref = ptr_ref_map.get(comp_rvar.typeref.id)
                        if comp_ptrref is None:
                            continue
                        comp_pi = pg_types.get_ptrref_storage_info(
                            comp_ptrref, resolve_type=False)

                        set_rvar.query.target_list.append(
                            pgast.ResTarget(
                                val=pgast.ColumnRef(name=[
                                    comp_rvar.alias.aliasname,
                                    comp_pi.column_name,
                                ]),
                                name=ptr_info.column_name,
                            ))
Exemplo n.º 26
0
def path_is_banned(
        path_id: irast.PathId, *,
        ctx: context.ContextLevel) -> bool:

    s_path_id = path_id.strip_weak_namespaces()
    return s_path_id in ctx.banned_paths and ctx.path_scope.is_visible(path_id)
Exemplo n.º 27
0
def ban_path(
        path_id: irast.PathId, *,
        ctx: context.ContextLevel) -> None:

    ctx.banned_paths.add(path_id.strip_weak_namespaces())
Exemplo n.º 28
0
def range_for_material_objtype(
        typeref: irast.TypeRef,
        path_id: irast.PathId,
        *,
        include_overlays: bool = True,
        include_descendants: bool = True,
        dml_source: Optional[irast.MutatingStmt] = None,
        ctx: context.CompilerContextLevel) -> pgast.PathRangeVar:

    env = ctx.env

    if typeref.material_type is not None:
        typeref = typeref.material_type

    table_schema_name, table_name = common.get_objtype_backend_name(
        typeref.id, typeref.module_id, catenate=False)

    if typeref.name_hint.module in {'cfg', 'sys'}:
        # Redirect all queries to schema tables to edgedbss
        table_schema_name = 'edgedbss'

    relation = pgast.Relation(
        schemaname=table_schema_name,
        name=table_name,
        path_id=path_id,
    )

    rvar: pgast.PathRangeVar = pgast.RelRangeVar(
        relation=relation,
        typeref=typeref,
        include_inherited=include_descendants,
        alias=pgast.Alias(aliasname=env.aliases.get(typeref.name_hint.name)))

    overlays = get_type_rel_overlays(typeref, dml_source=dml_source, ctx=ctx)
    if overlays and include_overlays:
        set_ops = []

        qry = pgast.SelectStmt()
        qry.from_clause.append(rvar)
        pathctx.put_path_value_rvar(qry, path_id, rvar, env=env)
        if path_id.is_objtype_path():
            pathctx.put_path_source_rvar(qry, path_id, rvar, env=env)
        pathctx.put_path_bond(qry, path_id)

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

        for op, cte, cte_path_id in overlays:
            rvar = pgast.RelRangeVar(
                relation=cte,
                typeref=typeref,
                alias=pgast.Alias(aliasname=env.aliases.get(hint=cte.name)))

            qry = pgast.SelectStmt(from_clause=[rvar], )

            pathctx.put_path_value_rvar(qry, cte_path_id, rvar, env=env)
            if path_id.is_objtype_path():
                pathctx.put_path_source_rvar(qry, cte_path_id, rvar, env=env)
            pathctx.put_path_bond(qry, cte_path_id)

            qry.view_path_id_map[path_id] = cte_path_id

            qry_rvar = pgast.RangeSubselect(
                subquery=qry,
                alias=pgast.Alias(aliasname=env.aliases.get(hint=cte.name)))

            qry2 = pgast.SelectStmt(from_clause=[qry_rvar])
            pathctx.put_path_value_rvar(qry2, path_id, qry_rvar, env=env)
            if path_id.is_objtype_path():
                pathctx.put_path_source_rvar(qry2, path_id, qry_rvar, env=env)
            pathctx.put_path_bond(qry2, path_id)

            if op == 'replace':
                op = 'union'
                set_ops = []
            set_ops.append((op, qry2))

        rvar = range_from_queryset(set_ops, typeref.name_hint, ctx=ctx)

    return rvar
Exemplo n.º 29
0
def _normalize_view_ptr_expr(
        shape_el: qlast.ShapeElement,
        view_scls: s_objtypes.ObjectType, *,
        path_id: irast.PathId,
        path_id_namespace: Optional[irast.WeakNamespace]=None,
        is_insert: bool=False,
        is_update: bool=False,
        from_default: bool=False,
        view_rptr: Optional[context.ViewRPtr]=None,
        ctx: context.ContextLevel) -> s_pointers.Pointer:
    steps = shape_el.expr.steps
    is_linkprop = False
    is_polymorphic = False
    is_mutation = is_insert or is_update
    # Pointers may be qualified by the explicit source
    # class, which is equivalent to Expr[IS Type].
    plen = len(steps)
    ptrsource: s_sources.Source = view_scls
    qlexpr: Optional[qlast.Expr] = None
    target_typexpr = None
    source: qlast.Base
    base_ptrcls_is_alias = False

    if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection):
        # Target type intersection: foo: Type
        target_typexpr = steps[-1].type
        plen -= 1
        steps = steps[:-1]

    if plen == 1:
        # regular shape
        lexpr = steps[0]
        assert isinstance(lexpr, qlast.Ptr)
        is_linkprop = lexpr.type == 'property'
        if is_linkprop:
            if view_rptr is None or view_rptr.ptrcls is None:
                raise errors.QueryError(
                    'invalid reference to link property '
                    'in top level shape', context=lexpr.context)
            assert isinstance(view_rptr.ptrcls, s_links.Link)
            ptrsource = view_rptr.ptrcls
        source = qlast.Source()
    elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection):
        # Source type intersection: [IS Type].foo
        source = qlast.Path(steps=[
            qlast.Source(),
            steps[0],
        ])
        lexpr = steps[1]
        ptype = steps[0].type
        if not isinstance(ptype, qlast.TypeName):
            raise errors.QueryError(
                'complex type expressions are not supported here',
                context=ptype.context,
            )
        source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx)
        if not isinstance(source_spec, s_objtypes.ObjectType):
            raise errors.QueryError(
                f'expected object type, got '
                f'{source_spec.get_verbosename(ctx.env.schema)}',
                context=ptype.context,
            )
        ptrsource = source_spec
        is_polymorphic = True
    else:  # pragma: no cover
        raise RuntimeError(
            f'unexpected path length in view shape: {len(steps)}')

    assert isinstance(lexpr, qlast.Ptr)
    ptrname = lexpr.ptr.name

    compexpr: Optional[qlast.Expr] = shape_el.compexpr
    if compexpr is None and is_insert and shape_el.elements:
        # Short shape form in INSERT, e.g
        #     INSERT Foo { bar: Spam { name := 'name' }}
        # is prohibited.
        raise errors.EdgeQLSyntaxError(
            "unexpected ':'", context=steps[-1].context)

    ptrcls: Optional[s_pointers.Pointer]

    if compexpr is None:
        ptrcls = setgen.resolve_ptr(
            ptrsource, ptrname, track_ref=lexpr, ctx=ctx)
        if is_polymorphic:
            ptrcls = schemactx.derive_ptr(
                ptrcls, view_scls,
                is_insert=is_insert,
                is_update=is_update,
                ctx=ctx)

        base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema)
        base_ptr_is_computable = base_ptrcls in ctx.source_map
        ptr_name = sn.QualName(
            module='__',
            name=ptrcls.get_shortname(ctx.env.schema).name,
        )

        base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx)
        base_is_singleton = False
        if base_cardinality is not None and base_cardinality.is_known():
            base_is_singleton = base_cardinality.is_single()

        if (
            shape_el.where
            or shape_el.orderby
            or shape_el.offset
            or shape_el.limit
            or base_ptr_is_computable
            or is_polymorphic
            or target_typexpr is not None
            or (ctx.implicit_limit and not base_is_singleton)
        ):

            if target_typexpr is None:
                qlexpr = qlast.Path(steps=[source, lexpr])
            else:
                qlexpr = qlast.Path(steps=[
                    source,
                    lexpr,
                    qlast.TypeIntersection(type=target_typexpr),
                ])

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            assert isinstance(qlexpr, qlast.SelectQuery)
            qlexpr.where = shape_el.where
            qlexpr.orderby = shape_el.orderby

            if shape_el.offset or shape_el.limit:
                qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True)
                qlexpr.offset = shape_el.offset
                qlexpr.limit = shape_el.limit

            if (
                (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt)
                and not qlexpr.limit
                and ctx.implicit_limit
                and not base_is_singleton
            ):
                qlexpr.limit = qlast.IntegerConstant(
                    value=str(ctx.implicit_limit),
                )

        if target_typexpr is not None:
            assert isinstance(target_typexpr, qlast.TypeName)
            intersector_type = schemactx.get_schema_type(
                target_typexpr.maintype, ctx=ctx)

            int_result = schemactx.apply_intersection(
                ptrcls.get_target(ctx.env.schema),  # type: ignore
                intersector_type,
                ctx=ctx,
            )

            ptr_target = int_result.stype
        else:
            _ptr_target = ptrcls.get_target(ctx.env.schema)
            assert _ptr_target
            ptr_target = _ptr_target

        ptr_cardinality = base_cardinality
        if ptr_cardinality is None or not ptr_cardinality.is_known():
            # We do not know the parent's pointer cardinality yet.
            ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls)
            ctx.env.pointer_specified_info[ptrcls] = (
                shape_el.cardinality, shape_el.required, shape_el.context)

        implicit_tid = has_implicit_type_computables(
            ptr_target,
            is_mutation=is_mutation,
            ctx=ctx,
        )

        if shape_el.elements or implicit_tid:
            sub_view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                is_insert=is_insert,
                is_update=is_update)

            sub_path_id = pathctx.extend_path_id(
                path_id,
                ptrcls=base_ptrcls,
                ns=ctx.path_id_namespace,
                ctx=ctx)

            ctx.path_scope.attach_path(sub_path_id,
                                       context=shape_el.context)

            if not isinstance(ptr_target, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'shapes cannot be applied to '
                    f'{ptr_target.get_verbosename(ctx.env.schema)}',
                    context=shape_el.context,
                )

            if is_update:
                for subel in shape_el.elements or []:
                    is_prop = (
                        isinstance(subel.expr.steps[0], qlast.Ptr) and
                        subel.expr.steps[0].type == 'property'
                    )
                    if not is_prop:
                        raise errors.QueryError(
                            'only references to link properties are allowed '
                            'in nested UPDATE shapes', context=subel.context)

                ptr_target = _process_view(
                    stype=ptr_target, path_id=sub_path_id,
                    path_id_namespace=path_id_namespace,
                    view_rptr=sub_view_rptr,
                    elements=shape_el.elements, is_update=True,
                    parser_context=shape_el.context,
                    ctx=ctx)
            else:
                ptr_target = _process_view(
                    stype=ptr_target, path_id=sub_path_id,
                    path_id_namespace=path_id_namespace,
                    view_rptr=sub_view_rptr,
                    elements=shape_el.elements,
                    parser_context=shape_el.context,
                    ctx=ctx)

    else:
        base_ptrcls = ptrcls = None

        if (is_mutation
                and ptrname not in ctx.special_computables_in_mutation_shape):
            # If this is a mutation, the pointer must exist.
            ptrcls = setgen.resolve_ptr(
                ptrsource, ptrname, track_ref=lexpr, ctx=ctx)

            base_ptrcls = ptrcls.get_bases(
                ctx.env.schema).first(ctx.env.schema)

            ptr_name = sn.QualName(
                module='__',
                name=ptrcls.get_shortname(ctx.env.schema).name,
            )

        else:
            ptr_name = sn.QualName(
                module='__',
                name=ptrname,
            )

            try:
                ptrcls = setgen.resolve_ptr(
                    ptrsource,
                    ptrname,
                    track_ref=False,
                    ctx=ctx,
                )

                base_ptrcls = ptrcls.get_bases(
                    ctx.env.schema).first(ctx.env.schema)
            except errors.InvalidReferenceError:
                # This is a NEW computable pointer, it's fine.
                pass

        qlexpr = astutils.ensure_qlstmt(compexpr)

        if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt)
                and ctx.implicit_limit
                and isinstance(qlexpr, qlast.OffsetLimitMixin)
                and not qlexpr.limit):
            qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit))

        with ctx.newscope(fenced=True) as shape_expr_ctx:
            # Put current pointer class in context, so
            # that references to link properties in sub-SELECT
            # can be resolved.  This is necessary for proper
            # evaluation of link properties on computable links,
            # most importantly, in INSERT/UPDATE context.
            shape_expr_ctx.view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                ptrcls_name=ptr_name,
                ptrcls_is_linkprop=is_linkprop,
                is_insert=is_insert,
                is_update=is_update,
            )

            shape_expr_ctx.defining_view = view_scls
            shape_expr_ctx.path_scope.unnest_fence = True
            shape_expr_ctx.partial_path_prefix = setgen.class_set(
                view_scls.get_bases(ctx.env.schema).first(ctx.env.schema),
                path_id=path_id, ctx=shape_expr_ctx)
            prefix_rptrref = path_id.rptr()
            if prefix_rptrref is not None:
                # Source path seems to contain multiple steps,
                # so set up a rptr for abbreviated link property
                # paths.
                src_path_id = path_id.src_path()
                assert src_path_id is not None
                ctx.env.schema, src_t = irtyputils.ir_typeref_to_type(
                    shape_expr_ctx.env.schema,
                    src_path_id.target,
                )
                prefix_rptr = irast.Pointer(
                    source=setgen.class_set(
                        src_t,
                        path_id=src_path_id,
                        ctx=shape_expr_ctx,
                    ),
                    target=shape_expr_ctx.partial_path_prefix,
                    ptrref=prefix_rptrref,
                    direction=s_pointers.PointerDirection.Outbound,
                )
                shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr

            if is_mutation and ptrcls is not None:
                shape_expr_ctx.expr_exposed = True
                shape_expr_ctx.empty_result_type_hint = \
                    ptrcls.get_target(ctx.env.schema)

            shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata(
                iterator_target=True,
            )
            irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx)

            if (
                shape_el.operation.op is qlast.ShapeOp.APPEND
                or shape_el.operation.op is qlast.ShapeOp.SUBTRACT
            ):
                if not is_update:
                    op = (
                        '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND
                        else '-='
                    )
                    raise errors.EdgeQLSyntaxError(
                        f"unexpected '{op}'",
                        context=shape_el.operation.context,
                    )

            irexpr.context = compexpr.context

            if base_ptrcls is None:
                base_ptrcls = shape_expr_ctx.view_rptr.base_ptrcls
                base_ptrcls_is_alias = shape_expr_ctx.view_rptr.ptrcls_is_alias

            if ptrcls is not None:
                ctx.env.schema = ptrcls.set_field_value(
                    ctx.env.schema, 'owned', True)

        ptr_cardinality = None
        ptr_target = inference.infer_type(irexpr, ctx.env)

        if (
            isinstance(ptr_target, s_types.Collection)
            and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None)
        ):
            # Record references to implicitly defined collection types,
            # so that the alias delta machinery can pick them up.
            ctx.env.created_schema_objects.add(ptr_target)

        anytype = ptr_target.find_any(ctx.env.schema)
        if anytype is not None:
            raise errors.QueryError(
                'expression returns value of indeterminate type',
                context=ctx.env.type_origins.get(anytype),
            )

        # Validate that the insert/update expression is
        # of the correct class.
        if is_mutation and ptrcls is not None:
            base_target = ptrcls.get_target(ctx.env.schema)
            assert base_target is not None
            if ptr_target.assignment_castable_to(
                    base_target,
                    schema=ctx.env.schema):
                # Force assignment casts if the target type is not a
                # subclass of the base type and the cast is not to an
                # object type.
                if not (
                    base_target.is_object_type()
                    or s_types.is_type_compatible(
                        base_target, ptr_target, schema=ctx.env.schema
                    )
                ):
                    qlexpr = astutils.ensure_qlstmt(qlast.TypeCast(
                        type=typegen.type_to_ql_typeref(base_target, ctx=ctx),
                        expr=compexpr,
                    ))
                    ptr_target = base_target

            else:
                expected = [
                    repr(str(base_target.get_displayname(ctx.env.schema)))
                ]

                ercls: Type[errors.EdgeDBError]
                if ptrcls.is_property(ctx.env.schema):
                    ercls = errors.InvalidPropertyTargetError
                else:
                    ercls = errors.InvalidLinkTargetError

                ptr_vn = ptrcls.get_verbosename(ctx.env.schema,
                                                with_parent=True)

                raise ercls(
                    f'invalid target for {ptr_vn}: '
                    f'{str(ptr_target.get_displayname(ctx.env.schema))!r} '
                    f'(expecting {" or ".join(expected)})'
                )

    if qlexpr is not None or ptrcls is None:
        src_scls: s_sources.Source

        if is_linkprop:
            # Proper checking was done when is_linkprop is defined.
            assert view_rptr is not None
            assert isinstance(view_rptr.ptrcls, s_links.Link)
            src_scls = view_rptr.ptrcls
        else:
            src_scls = view_scls

        if ptr_target.is_object_type():
            base = ctx.env.get_track_schema_object(
                sn.QualName('std', 'link'), expr=None)
        else:
            base = ctx.env.get_track_schema_object(
                sn.QualName('std', 'property'), expr=None)

        if base_ptrcls is not None:
            derive_from = base_ptrcls
        else:
            derive_from = base

        derived_name = schemactx.derive_view_name(
            base_ptrcls,
            derived_name_base=ptr_name,
            derived_name_quals=[str(src_scls.get_name(ctx.env.schema))],
            ctx=ctx,
        )

        existing = ctx.env.schema.get(
            derived_name, default=None, type=s_pointers.Pointer)
        if existing is not None:
            existing_target = existing.get_target(ctx.env.schema)
            assert existing_target is not None
            if ctx.recompiling_schema_alias:
                ptr_cardinality = existing.get_cardinality(ctx.env.schema)
            if ptr_target == existing_target:
                ptrcls = existing
            elif ptr_target.implicitly_castable_to(
                    existing_target, ctx.env.schema):
                ctx.env.schema = existing.set_target(
                    ctx.env.schema, ptr_target)
                ptrcls = existing
            else:
                vnp = existing.get_verbosename(
                    ctx.env.schema, with_parent=True)

                t1_vn = existing_target.get_verbosename(ctx.env.schema)
                t2_vn = ptr_target.get_verbosename(ctx.env.schema)

                if compexpr is not None:
                    source_context = compexpr.context
                else:
                    source_context = shape_el.expr.steps[-1].context
                raise errors.SchemaError(
                    f'cannot redefine {vnp} as {t2_vn}',
                    details=f'{vnp} is defined as {t1_vn}',
                    context=source_context,
                )
        else:
            ptrcls = schemactx.derive_ptr(
                derive_from, src_scls, ptr_target,
                is_insert=is_insert,
                is_update=is_update,
                derived_name=derived_name,
                ctx=ctx)

    elif ptrcls.get_target(ctx.env.schema) != ptr_target:
        ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target)

    assert ptrcls is not None

    if qlexpr is None:
        # This is not a computable, just a pointer
        # to a nested shape.  Have it reuse the original
        # pointer name so that in `Foo.ptr.name` and
        # `Foo { ptr: {name}}` are the same path.
        path_id_name = base_ptrcls.get_name(ctx.env.schema)
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'path_id_name', path_id_name
        )

    if qlexpr is not None:
        ctx.source_map[ptrcls] = irast.ComputableInfo(
            qlexpr=qlexpr,
            context=ctx,
            path_id=path_id,
            path_id_ns=path_id_namespace,
            shape_op=shape_el.operation.op,
        )

    if compexpr is not None or is_polymorphic:
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema,
            'computable',
            True,
        )

        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema,
            'owned',
            True,
        )

    if ptr_cardinality is not None:
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'cardinality', ptr_cardinality)
    else:
        if qlexpr is None and ptrcls is not base_ptrcls:
            ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls)

        base_cardinality = None
        base_required = False
        if base_ptrcls is not None and not base_ptrcls_is_alias:
            base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx)
            base_required = base_ptrcls.get_required(ctx.env.schema)

        if base_cardinality is None or not base_cardinality.is_known():
            specified_cardinality = shape_el.cardinality
            specified_required = shape_el.required
        else:
            specified_cardinality = base_cardinality
            specified_required = base_required

            if (shape_el.cardinality is not None
                    and base_ptrcls is not None
                    and shape_el.cardinality != base_cardinality):
                base_src = base_ptrcls.get_source(ctx.env.schema)
                assert base_src is not None
                base_src_name = base_src.get_verbosename(ctx.env.schema)
                raise errors.SchemaError(
                    f'cannot redefine the cardinality of '
                    f'{ptrcls.get_verbosename(ctx.env.schema)}: '
                    f'it is defined as {base_cardinality.as_ptr_qual()!r} '
                    f'in the base {base_src_name}',
                    context=compexpr and compexpr.context,
                )
            # The required flag may be inherited from the base
            specified_required = shape_el.required or base_required

        ctx.env.pointer_specified_info[ptrcls] = (
            specified_cardinality, specified_required, shape_el.context)

        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown)

    if (
        ptrcls.is_protected_pointer(ctx.env.schema)
        and qlexpr is not None
        and not from_default
        and not ctx.env.options.allow_writing_protected_pointers
    ):
        ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
        if is_polymorphic:
            msg = (f'cannot access {ptrcls_sn.name} on a polymorphic '
                   f'shape element')
        else:
            msg = f'cannot assign to {ptrcls_sn.name}'
        raise errors.QueryError(msg, context=shape_el.context)

    if is_update and ptrcls.get_readonly(ctx.env.schema):
        raise errors.QueryError(
            f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: '
            f'it is declared as read-only',
            context=compexpr and compexpr.context,
        )

    return ptrcls
Exemplo n.º 30
0
def _get_path_output(rel: pgast.BaseRelation,
                     path_id: irast.PathId,
                     *,
                     aspect: str,
                     allow_nullable: bool = True,
                     ptr_info: typing.Optional[
                         pg_types.PointerStorageInfo] = None,
                     env: context.Environment) -> pgast.OutputVar:

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

    ref: pgast.BaseExpr
    alias = None
    rptr = path_id.rptr()
    if rptr is not None and irtyputils.is_id_ptrref(rptr):
        # A value reference to Object.id is the same as a value
        # reference to the Object itself.
        src_path_id = path_id.src_path()
        id_output = rel.path_outputs.get((src_path_id, 'value'))
        if id_output is not None:
            _put_path_output_var(rel, path_id, aspect, id_output, env=env)
            return id_output

    if is_terminal_relation(rel):
        return _get_rel_path_output(rel,
                                    path_id,
                                    aspect=aspect,
                                    ptr_info=ptr_info,
                                    env=env)

    assert isinstance(rel, pgast.Query)
    if is_values_relation(rel):
        # The VALUES() construct seems to always expose its
        # value as "column1".
        alias = 'column1'
        ref = pgast.ColumnRef(name=[alias])
    else:
        ref = get_path_var(rel, path_id, aspect=aspect, env=env)

    other_output = find_path_output(rel, path_id, ref, env=env)
    if other_output is not None:
        _put_path_output_var(rel, path_id, aspect, other_output, env=env)
        return other_output

    if isinstance(ref, pgast.TupleVarBase):
        elements = []
        for el in ref.elements:
            el_path_id = reverse_map_path_id(el.path_id, rel.view_path_id_map)

            try:
                # Similarly to get_path_var(), check for outer path_id
                # first for tuple serialized var disambiguation.
                element = _get_path_output(rel,
                                           el_path_id,
                                           aspect=aspect,
                                           allow_nullable=False,
                                           env=env)
            except LookupError:
                element = get_path_output(rel,
                                          el_path_id,
                                          aspect=aspect,
                                          allow_nullable=False,
                                          env=env)

            elements.append(
                pgast.TupleElementBase(path_id=el_path_id, name=element))

        result = pgast.TupleVarBase(elements=elements, named=ref.named)

    else:
        if astutils.is_set_op_query(rel):
            assert isinstance(ref, pgast.OutputVar)
            result = astutils.strip_output_var(ref)
        else:
            assert isinstance(rel, pgast.ReturningQuery), \
                "expected ReturningQuery"

            if alias is None:
                alias = get_path_output_alias(path_id, aspect, env=env)

            restarget = pgast.ResTarget(name=alias,
                                        val=ref,
                                        ser_safe=getattr(
                                            ref, 'ser_safe', False))
            rel.target_list.append(restarget)

            nullable = is_nullable(ref, env=env)

            optional = None
            if isinstance(ref, pgast.ColumnRef):
                optional = ref.optional

            if nullable and not allow_nullable:
                var = get_path_var(rel, path_id, aspect=aspect, env=env)
                rel.where_clause = astutils.extend_binop(
                    rel.where_clause, pgast.NullTest(arg=var, negated=True))
                nullable = False

            result = pgast.ColumnRef(name=[alias],
                                     nullable=nullable,
                                     optional=optional)

    _put_path_output_var(rel, path_id, aspect, result, env=env)
    if (path_id.is_objtype_path()
            and not isinstance(result, pgast.TupleVarBase)):
        equiv_aspect = None
        if aspect == 'identity':
            equiv_aspect = 'value'
        elif aspect == 'value':
            equiv_aspect = 'identity'

        if (equiv_aspect is not None
                and (path_id, equiv_aspect) not in rel.path_outputs):
            _put_path_output_var(rel, path_id, equiv_aspect, result, env=env)

    return result