Beispiel #1
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)
Beispiel #2
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()
Beispiel #3
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
Beispiel #4
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,
                            ))
Beispiel #5
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
Beispiel #6
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
Beispiel #7
0
def _normalize_view_ptr_expr(shape_el: qlast.ShapeElement,
                             view_scls: s_types.Type,
                             *,
                             path_id: irast.PathId,
                             path_id_namespace: Optional[
                                 irast.WeakNamespace] = None,
                             is_insert: bool = False,
                             is_update: 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 = view_scls
    qlexpr = None
    target_typexpr = None
    source: qlast.Base

    if plen >= 2 and isinstance(steps[-1], qlast.TypeIndirection):
        # Target type indirection: 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:
                raise errors.QueryError(
                    'invalid reference to link property '
                    'in top level shape',
                    context=lexpr.context)
            ptrsource = view_rptr.ptrcls
        source = qlast.Source()
    elif plen == 2 and isinstance(steps[0], qlast.TypeIndirection):
        # Source type indirection: [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,
            )
        ptrsource = schemactx.get_schema_type(ptype.maintype, ctx=ctx)
        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 = 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)

    if compexpr is None:
        ptrcls = setgen.resolve_ptr(ptrsource, ptrname, 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.Name(
            module='__',
            name=ptrcls.get_shortname(ctx.env.schema).name,
        )

        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):

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

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            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 target_typexpr is not None:
            ptr_target = schemactx.get_schema_type(target_typexpr.maintype,
                                                   ctx=ctx)
        else:
            ptr_target = ptrcls.get_target(ctx.env.schema)

        if base_ptrcls in ctx.pending_cardinality:
            # We do not know the parent's pointer cardinality yet.
            ptr_cardinality = None
            ctx.pointer_derivation_map[base_ptrcls].append(ptrcls)
            stmtctx.pend_pointer_cardinality_inference(
                ptrcls=ptrcls,
                specified_card=shape_el.cardinality,
                source_ctx=shape_el.context,
                ctx=ctx)
        else:
            ptr_cardinality = base_ptrcls.get_cardinality(ctx.env.schema)

        implicit_tid = has_implicit_tid(
            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,
                                                 target=ptrcls.get_target(
                                                     ctx.env.schema),
                                                 ns=ctx.path_id_namespace,
                                                 ctx=ctx)

            ctx.path_scope.attach_path(sub_path_id)

            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,
                                           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,
                                           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, ctx=ctx)

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

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

        else:
            # Otherwise, assume no pointer inheritance.
            # Every computable is a new pointer derived from
            # std::link or std::property.  There is one exception:
            # pointer aliases (Foo {some := Foo.other}), where `foo`
            # gets derived from `Foo.other`.  This logic is applied
            # in compile_query_subject() by populating the base_ptrcls.
            ptr_name = sn.Name(
                module='__',
                name=ptrname,
            )

        qlexpr = astutils.ensure_qlstmt(compexpr)

        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 = True
            shape_expr_ctx.path_scope.unnest_fence = True
            shape_expr_ctx.partial_path_prefix = setgen.class_set(
                view_scls, 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()
                prefix_rptr = irast.Pointer(
                    source=setgen.class_set(
                        irtyputils.ir_typeref_to_type(
                            shape_expr_ctx.env.schema,
                            src_path_id.target,
                        ),
                        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)

            irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx)

            irexpr.context = compexpr.context

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

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

        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 ptr_target.issubclass(ctx.env.schema, base_target)):
                    qlexpr = astutils.ensure_qlstmt(
                        qlast.TypeCast(
                            type=astutils.type_to_ql_typeref(
                                base_target, schema=ctx.env.schema),
                            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:
        if is_linkprop:
            # Proper checking was done when is_linkprop is defined.
            assert view_rptr is not None
            src_scls = view_rptr.ptrcls
        else:
            src_scls = view_scls

        if ptr_target.is_object_type():
            base = ctx.env.get_track_schema_object('std::link')
        else:
            base = ctx.env.get_track_schema_object('std::property')

        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=[src_scls.get_name(ctx.env.schema)],
            ctx=ctx)

        existing = ctx.env.schema.get(derived_name, None)
        if existing is not None:
            existing_target = existing.get_target(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:
                target_rptr_set = (ptr_target.get_rptr(ctx.env.schema)
                                   is not None)

                if target_rptr_set:
                    ctx.env.schema = ptr_target.set_field_value(
                        ctx.env.schema,
                        'rptr',
                        None,
                    )

                ctx.env.schema = existing.delete(ctx.env.schema)
                ptrcls = schemactx.derive_ptr(derive_from,
                                              src_scls,
                                              ptr_target,
                                              is_insert=is_insert,
                                              is_update=is_update,
                                              derived_name=derived_name,
                                              inheritance_merge=False,
                                              ctx=ctx)

                if target_rptr_set:
                    ctx.env.schema = ptr_target.set_field_value(
                        ctx.env.schema,
                        'rptr',
                        ptrcls,
                    )
        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] = (qlexpr, ctx, path_id, path_id_namespace)

    if not is_mutation:
        if ptr_cardinality is None:
            if qlexpr is None and ptrcls is not base_ptrcls:
                ctx.pointer_derivation_map[base_ptrcls].append(ptrcls)

            stmtctx.pend_pointer_cardinality_inference(
                ptrcls=ptrcls,
                specified_card=shape_el.cardinality,
                source_ctx=shape_el.context,
                ctx=ctx)

            ctx.env.schema = ptrcls.set_field_value(ctx.env.schema,
                                                    'cardinality', None)
        else:
            ctx.env.schema = ptrcls.set_field_value(ctx.env.schema,
                                                    'cardinality',
                                                    ptr_cardinality)

    if ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None:
        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)

    return ptrcls
Beispiel #8
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]

    if astutils.is_set_op_query(rel):
        return _get_path_var_in_setop(rel, path_id, aspect=aspect, env=env)

    ptrref = path_id.rptr()
    is_type_intersection = path_id.is_type_intersection_path()

    src_path_id: Optional[irast.PathId] = None
    if ptrref is not None and not is_type_intersection:
        ptr_info = pg_types.get_ptrref_storage_info(
            ptrref, resolve_type=False, link_bias=False, allow_missing=True)
        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()
            assert src_path_id is not None
            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_intersection_path():
                    # Skip type intersection step(s).
                    src_pid = pid.src_path()
                    if src_pid is not None:
                        src_rptr = src_pid.rptr()
                        pid = src_pid
                    else:
                        break

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

    else:
        ptr_info = None
        ptr_dir = None

    var: Optional[pgast.BaseExpr]

    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.source_ptr is not None:
        if ptr_info and 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.
            _prefix_pid = path_id.src_path()
            assert _prefix_pid is not None
            src_path_id = _prefix_pid.src_path()

    elif is_type_intersection:
        src_path_id = path_id

    assert src_path_id is not None

    # Find which rvar will have path_id as an output
    src_aspect, rel_rvar, found_path_var = _find_rel_rvar(
        rel, path_id, src_path_id, aspect=aspect, env=env)

    if found_path_var:
        return found_path_var

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

    if isinstance(rel_rvar, pgast.IntersectionRangeVar):
        if (
            (path_id.is_objtype_path() and src_path_id == path_id)
            or (ptrref is not None and irtyputils.is_id_ptrref(ptrref))
        ):
            rel_rvar = rel_rvar.component_rvars[-1]
        else:
            # Intersection rvars are basically JOINs of the relevant
            # parts of the type intersection, and so we need to make
            # sure we pick the correct component relation of that JOIN.
            rel_rvar = _find_rvar_in_intersection_by_typeref(
                path_id,
                rel_rvar.component_rvars,
            )

    source_rel = rel_rvar.query

    if isinstance(ptrref, irast.PointerRef) and rel_rvar.typeref is not None:
        actual_ptrref = irtyputils.maybe_find_actual_ptrref(
            rel_rvar.typeref, ptrref)

        if actual_ptrref is not None:
            ptr_info = pg_types.get_ptrref_storage_info(
                actual_ptrref, resolve_type=False, link_bias=False)

    outvar = get_path_output(
        source_rel, 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
Beispiel #9
0
def _compile_qlexpr(
    qlexpr: qlast.Base,
    view_scls: s_objtypes.ObjectType,
    *,
    ptrcls: Optional[s_pointers.Pointer],
    ptrsource: s_sources.Source,
    path_id: irast.PathId,
    ptr_name: sn.QualName,
    is_insert: bool,
    is_update: bool,
    is_linkprop: bool,

    ctx: context.ContextLevel,
) -> Tuple[irast.Set, context.ViewRPtr]:

    is_mutation = is_insert or is_update

    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)

        irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx)

    return irexpr, shape_expr_ctx.view_rptr
Beispiel #10
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_intersection = path_id.is_type_intersection_path()

    src_path_id: Optional[irast.PathId] = None
    if ptrref is not None and not is_type_intersection:
        ptr_info = pg_types.get_ptrref_storage_info(
            ptrref, resolve_type=False, link_bias=False, allow_missing=True)
        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()
            assert src_path_id is not None
            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_intersection_path():
                    # Skip type intersection step(s).
                    src_pid = pid.src_path()
                    if src_pid is not None:
                        src_rptr = src_pid.rptr()
                        pid = src_pid
                    else:
                        break

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

    else:
        ptr_info = None
        ptr_dir = None

    var: Optional[pgast.BaseExpr]

    if astutils.is_set_op_query(rel):
        # We disable the find_path_output optimizaiton when doing
        # UNIONs to avoid situations where they have different numbers
        # of columns.
        cb = functools.partial(
            get_path_output_or_null,
            env=env,
            disable_output_fusion=True,
            path_id=path_id,
            aspect=aspect)

        outputs = astutils.for_each_query_in_set(rel, cb)

        first: Optional[pgast.OutputVar] = 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}')

        if first is None:
            raise AssertionError(
                f'union did not produce any outputs')

        # 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.source_ptr is not None:
        if ptr_info and 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.
            _prefix_pid = path_id.src_path()
            assert _prefix_pid is not None
            src_path_id = _prefix_pid.src_path()

    elif is_type_intersection:
        src_path_id = path_id

    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

    assert src_path_id is not 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:
                _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)

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

    if isinstance(rel_rvar, pgast.IntersectionRangeVar):
        if (
            (path_id.is_objtype_path() and src_path_id == path_id)
            or (ptrref is not None and irtyputils.is_id_ptrref(ptrref))
        ):
            rel_rvar = rel_rvar.component_rvars[-1]
        else:
            # Intersection rvars are basically JOINs of the relevant
            # parts of the type intersection, and so we need to make
            # sure we pick the correct component relation of that JOIN.
            rel_rvar = _find_rvar_in_intersection_by_typeref(
                path_id,
                rel_rvar.component_rvars,
            )

    source_rel = rel_rvar.query

    if isinstance(ptrref, irast.PointerRef) and rel_rvar.typeref is not None:
        actual_ptrref = irtyputils.maybe_find_actual_ptrref(
            rel_rvar.typeref, ptrref)

        if actual_ptrref is not None:
            ptr_info = pg_types.get_ptrref_storage_info(
                actual_ptrref, resolve_type=False, link_bias=False)

    outvar = get_path_output(
        source_rel, 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