コード例 #1
0
ファイル: relctx.py プロジェクト: mcaramma/edgedb
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
コード例 #2
0
ファイル: pathctx.py プロジェクト: mcaramma/edgedb
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(aspect)
コード例 #3
0
ファイル: relctx.py プロジェクト: mcaramma/edgedb
def include_rvar(stmt: pgast.Query,
                 rvar: pgast.BaseRangeVar,
                 path_id: irast.PathId,
                 *,
                 overwrite_path_rvar: bool = False,
                 ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    """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 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,
                                 aspects=aspects,
                                 ctx=ctx)
コード例 #4
0
ファイル: pathctx.py プロジェクト: mcaramma/edgedb
def reverse_map_path_id(
        path_id: irast.PathId,
        path_id_map: typing.Dict[irast.PathId, irast.PathId]) -> irast.PathId:
    for outer_id, inner_id in path_id_map.items():
        new_path_id = path_id.replace_prefix(inner_id, outer_id)
        if new_path_id != path_id:
            path_id = new_path_id
            break

    return path_id
コード例 #5
0
ファイル: pathctx.py プロジェクト: versada/edgedb
def _get_rel_path_output(rel: pgast.BaseRelation,
                         path_id: irast.PathId,
                         *,
                         aspect: str,
                         ptr_info: typing.Optional[
                             pg_types.PointerStorageInfo] = None,
                         env: context.Environment) -> pgast.OutputVar:

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

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

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

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

    ptrcls = path_id.rptr()

    if ptrcls is None:
        raise ValueError(
            f'could not resolve trailing pointer class for {path_id}')

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

    result = pgast.ColumnRef(name=[ptr_info.column_name],
                             nullable=rel.nullable or not ptrcls.required)
    rel.path_outputs[path_id, aspect] = result
    return result
コード例 #6
0
ファイル: pathctx.py プロジェクト: mcaramma/edgedb
def get_path_output_alias(path_id: irast.PathId, aspect: str, *,
                          env: context.Environment) -> str:
    rptr = path_id.rptr()
    if rptr is not None:
        ptrname = rptr.shortname
        alias_base = ptrname.name
    elif isinstance(path_id.target, s_types.Collection):
        alias_base = path_id.target.schema_name
    else:
        alias_base = path_id.target.name.name

    return env.aliases.get(f'{alias_base}_{aspect}')
コード例 #7
0
ファイル: relctx.py プロジェクト: dungeon2567/edgedb
def include_rvar(
        stmt: pgast.Query, rvar: pgast.BaseRangeVar,
        path_id: irast.PathId, *,
        overwrite_path_rvar: bool=False,
        pull_namespace: bool=True,
        aspects: typing.Optional[typing.Iterable[str]]=None,
        ctx: context.CompilerContextLevel) -> pgast.BaseRangeVar:
    """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)
コード例 #8
0
ファイル: pathctx.py プロジェクト: mcaramma/edgedb
def get_path_var(rel: pgast.BaseRelation, path_id: irast.PathId, *,
                 aspect: str, env: context.Environment) -> pgast.OutputVar:
    """Return an OutputVar for a given *path_id* in a given *rel*."""
    if isinstance(rel, pgast.CommonTableExpr):
        rel = rel.query

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

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

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

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

    else:
        ptr_info = None
        src_path_id = None
        ptr_dir = None

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

        outputs = astutils.for_each_query_in_set(rel, cb)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    source_rel = rel_rvar.query

    drilldown_path_id = map_path_id(path_id, rel.view_path_id_map)

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

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

        drilldown_path_id = map_path_id(drilldown_path_id, path_id_map)

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

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

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

    return var
コード例 #9
0
ファイル: pathctx.py プロジェクト: mcaramma/edgedb
def _get_rel_path_output(rel: pgast.BaseRelation,
                         path_id: irast.PathId,
                         *,
                         aspect: str,
                         ptr_info: typing.Optional[
                             pg_types.PointerStorageInfo] = None,
                         env: context.Environment) -> pgast.OutputVar:

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

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

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

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

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

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

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

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

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

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

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

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

        result = pgast.ColumnRef(name=[ptr_info.column_name],
                                 nullable=not ptrcls.required)
    rel.path_outputs[path_id, aspect] = result
    return result
コード例 #10
0
ファイル: viewgen.py プロジェクト: 1st1/edgedb
def _normalize_view_ptr_expr(
        shape_el: qlast.ShapeElement,
        view_scls: s_nodes.Node,
        *,
        path_id: irast.PathId,
        is_insert: bool = False,
        is_update: bool = False,
        view_rptr: typing.Optional[context.ViewRPtr] = None,
        ctx: context.CompilerContext) -> s_pointers.Pointer:
    steps = shape_el.expr.steps
    is_linkprop = False
    is_mutation = is_insert or is_update
    # Pointers may be qualified by the explicit source
    # class, which is equivalent to Expr[IS Type].
    is_polymorphic = len(steps) == 2
    scls = view_scls.peel_view()
    ptrsource = scls
    qlexpr = None

    if is_polymorphic:
        source = qlast.TypeFilter(expr=qlast.Path(steps=[qlast.Source()]),
                                  type=qlast.TypeName(maintype=steps[0]))
        lexpr = steps[1]
        ptrsource = schemactx.get_schema_type(steps[0], ctx=ctx)
    elif len(steps) == 1:
        lexpr = steps[0]
        is_linkprop = lexpr.type == 'property'
        if is_linkprop:
            if view_rptr is None:
                raise errors.EdgeQLError(
                    'invalid reference to link property '
                    'in top level shape',
                    context=lexpr.context)
            if view_rptr.ptrcls is None:
                derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx)
            ptrsource = scls = view_rptr.ptrcls
        source = qlast.Source()
    else:
        raise RuntimeError(
            f'unexpected path length in view shape: {len(steps)}')

    ptrname = (lexpr.ptr.module, lexpr.ptr.name)
    ptrcls_is_derived = False

    compexpr = shape_el.compexpr
    if compexpr is None and is_insert and shape_el.elements:
        # Nested insert short form:
        #     INSERT Foo { bar: Spam { name := 'name' }}
        # Expand to:
        #     INSERT Foo { bar := (INSERT Spam { name := 'name' }) }
        if lexpr.target is not None:
            ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx)
        else:
            ptr_target = None

        base_ptrcls = ptrcls = setgen.resolve_ptr(
            ptrsource,
            ptrname,
            s_pointers.PointerDirection.Outbound,
            target=ptr_target,
            ctx=ctx)

        compexpr = qlast.InsertQuery(subject=qlast.Path(steps=[
            qlast.ObjectRef(name=ptrcls.target.name.name,
                            module=ptrcls.target.name.module)
        ]),
                                     shape=shape_el.elements)

    if compexpr is None:
        if lexpr.target is not None:
            ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx)
        else:
            ptr_target = None

        base_ptrcls = ptrcls = setgen.resolve_ptr(
            ptrsource,
            ptrname,
            s_pointers.PointerDirection.Outbound,
            target=ptr_target,
            ctx=ctx)

        base_ptr_is_computable = ptrcls in ctx.source_map

        if ptr_target is not None and ptr_target != base_ptrcls.target:
            # This happens when a union type target is narrowed by an
            # [IS Type] construct.  Since the derived pointer will have
            # the correct target, we don't need to do anything, but
            # remove the [IS] qualifier to prevent recursion.
            lexpr.target = None
        else:
            ptr_target = ptrcls.target

        if ptrcls in ctx.pending_cardinality:
            # We do not know the parent's pointer cardinality yet.
            ptr_cardinality = None
        else:
            ptr_cardinality = ptrcls.cardinality

        if shape_el.elements:
            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 = path_id.extend(ptrcls, target=ptrcls.target)
            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.EdgeQLError(
                            'only references to link properties are allowed '
                            'in nested UPDATE shapes',
                            context=subel.context)

                ptr_target = _process_view(scls=ptr_target,
                                           path_id=sub_path_id,
                                           view_rptr=sub_view_rptr,
                                           elements=shape_el.elements,
                                           is_update=True,
                                           ctx=ctx)
            else:
                ptr_target = _process_view(scls=ptr_target,
                                           path_id=sub_path_id,
                                           view_rptr=sub_view_rptr,
                                           elements=shape_el.elements,
                                           ctx=ctx)

            ptrcls = sub_view_rptr.derived_ptrcls
            if ptrcls is None:
                ptrcls_is_derived = False
                ptrcls = sub_view_rptr.ptrcls
            else:
                ptrcls_is_derived = True

        if (shape_el.where or shape_el.orderby or shape_el.offset
                or shape_el.limit or base_ptr_is_computable or is_polymorphic):

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

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            qlexpr.where = shape_el.where
            qlexpr.orderby = shape_el.orderby
            qlexpr.offset = shape_el.offset
            qlexpr.limit = shape_el.limit
    else:
        try:
            base_ptrcls = ptrcls = setgen.resolve_ptr(
                ptrsource,
                ptrname,
                s_pointers.PointerDirection.Outbound,
                ctx=ctx)

            ptr_name = ptrcls.shortname
        except errors.EdgeQLReferenceError:
            if is_mutation:
                raise

            base_ptrcls = ptrcls = None

            ptr_module = (ptrname[0] or ctx.derived_target_module
                          or scls.name.module)

            ptr_name = sn.SchemaName(module=ptr_module, name=ptrname[1])

        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.path_scope.unnest_fence = True

            if is_mutation:
                shape_expr_ctx.expr_exposed = True

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

            irexpr.context = compexpr.context

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

            derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls
            if derived_ptrcls is not None:
                ptrcls_is_derived = True
                ptrcls = derived_ptrcls

        ptr_cardinality = None

        ptr_target = irutils.infer_type(irexpr, ctx.schema)
        if ptr_target is None:
            msg = 'cannot determine expression result type'
            raise errors.EdgeQLError(msg, context=shape_el.context)

        if is_mutation and not ptr_target.assignment_castable_to(
                base_ptrcls.target, schema=ctx.schema):
            # Validate that the insert/update expression is
            # of the correct class.
            lname = f'({ptrsource.name}).{ptrcls.shortname.name}'
            expected = [repr(str(base_ptrcls.target.name))]
            raise edgedb_error.InvalidPointerTargetError(
                f'invalid target for link {str(lname)!r}: '
                f'{str(ptr_target.name)!r} (expecting '
                f'{" or ".join(expected)})')

    if qlexpr is not None or ptr_target is not ptrcls.target:
        if not ptrcls_is_derived:
            if is_linkprop:
                rptrcls = view_rptr.derived_ptrcls
                if rptrcls is None:
                    rptrcls = derive_ptrcls(view_rptr,
                                            target_scls=view_scls,
                                            ctx=ctx)

                src_scls = rptrcls
            else:
                src_scls = view_scls

            ptrcls = schemactx.derive_view(ptrcls,
                                           src_scls,
                                           ptr_target,
                                           is_insert=is_insert,
                                           is_update=is_update,
                                           derived_name_quals=[view_scls.name],
                                           ctx=ctx)

        if qlexpr is not None:
            ctx.source_map[ptrcls] = (qlexpr, ctx)
            ptrcls.computable = True

    if not is_mutation:
        if ptr_cardinality is None:
            if compexpr is not None:
                ctx.pending_cardinality.add(ptrcls)
            elif ptrcls is not base_ptrcls:
                ctx.pointer_derivation_map[base_ptrcls].append(ptrcls)

            ptrcls.cardinality = None
        else:
            ptrcls.cardinality = ptr_cardinality

    if ptrcls.is_protected_pointer() and qlexpr is not None:
        msg = f'cannot assign to {ptrcls.shortname.name}'
        raise errors.EdgeQLError(msg, context=shape_el.context)

    return ptrcls