Ejemplo n.º 1
0
    def visit_Set(self, node):
        if node.expr is not None:
            result = self.visit(node.expr)
        else:
            links = []

            while node.rptr and (not node.show_as_anchor
                                 or self.context.inline_anchors):
                rptr = node.rptr
                ptrcls = rptr.ptrcls
                pname = ptrcls.shortname

                if isinstance(rptr.target.scls, s_objtypes.ObjectType):
                    target = rptr.target.scls.shortname
                    target = qlast.ObjectRef(name=target.name,
                                             module=target.module)
                else:
                    target = None

                link = qlast.Ptr(ptr=qlast.ObjectRef(name=pname.name, ),
                                 direction=rptr.direction,
                                 target=target)
                if isinstance(ptrcls.source, s_links.Link):
                    link.type = 'property'
                links.append(link)

                node = node.rptr.source

            result = qlast.Path()

            if node.show_as_anchor and not self.context.inline_anchors:
                if issubclass(node.show_as_anchor, qlast.Expr):
                    step = node.show_as_anchor()
                else:
                    step = qlast.ObjectRef(name=node.show_as_anchor)
            else:
                step = qlast.ObjectRef(name=node.scls.shortname.name,
                                       module=node.scls.shortname.module)

            result.steps.append(step)
            result.steps.extend(reversed(links))

        if node.shape:
            result = qlast.Shape(expr=result, elements=[])

            for el in node.shape:
                rptr = el.rptr
                ptrcls = rptr.ptrcls
                pn = ptrcls.shortname

                pn = qlast.ShapeElement(expr=qlast.Path(steps=[
                    qlast.Ptr(ptr=qlast.ObjectRef(name=pn.name),
                              direction=rptr.direction)
                ]))

                result.elements.append(pn)

        return result
Ejemplo n.º 2
0
    def reduce_PathStepName(self, *kids):
        from edgedb.lang.schema import pointers as s_pointers

        self.val = qlast.Path(steps=[
            qlast.Ptr(ptr=kids[0].val,
                      direction=s_pointers.PointerDirection.Outbound)
        ])
Ejemplo n.º 3
0
 def reduce_NodeName_Shape(self, *kids):
     self.val = qlast.Shape(expr=qlast.Path(steps=[
         qlast.ObjectRef(name=kids[0].val.name,
                         module=kids[0].val.module,
                         context=kids[0].context)
     ]),
                            elements=kids[1].val)
Ejemplo n.º 4
0
    def reduce_Expr_PathStep(self, *kids):
        path = kids[0].val
        if not isinstance(path, qlast.Path):
            path = qlast.Path(steps=[path])

        path.steps.append(kids[1].val)
        self.val = path
Ejemplo n.º 5
0
    def reduce_Expr_DOT_FCONST(self, *kids):
        # this is a valid link-like syntax for accessing unnamed tuples
        path = kids[0].val
        if not isinstance(path, qlast.Path):
            path = qlast.Path(steps=[path])

        path.steps.extend(self._float_to_path(kids[2], kids[1].context))
        self.val = path
Ejemplo n.º 6
0
    def visit_TypeRef(self, node):
        # Bare TypeRef only appears as rhs of IS [NOT] and is always
        # an object type reference.
        mtn = node.maintype

        result = qlast.Path(
            steps=[qlast.ObjectRef(module=mtn.module, name=mtn.name)])

        return result
Ejemplo n.º 7
0
    def _visit_query(self, node):
        # populate input variables with defaults, where applicable
        if node.variables:
            self.visit(node.variables)

        # base Query needs to be configured specially
        base = self._context.gql_schema.get('Query',
                                            modules=self._context.modules)

        # special treatment of the selection_set, different from inner
        # recursion
        query = qlast.SelectQuery(result=qlast.Shape(expr=qlast.Path(
            steps=[qlast.ObjectRef(name='Query', module='graphql')]),
                                                     elements=[]), )

        self._context.fields.append({})
        self._context.path.append([Step(None, base)])
        query.result.elements = self.visit(node.selection_set)
        self._context.fields.pop()
        self._context.path.pop()

        return query
Ejemplo n.º 8
0
def compile_result_clause(
        result: qlast.Base, *,
        view_scls: typing.Optional[s_types.Type]=None,
        view_rptr: typing.Optional[context.ViewRPtr]=None,
        view_name: typing.Optional[s_name.SchemaName]=None,
        result_alias: typing.Optional[str]=None,
        ctx: context.ContextLevel) -> irast.Set:
    with ctx.new() as sctx:
        sctx.clause = 'result'
        if sctx.stmt is ctx.toplevel_stmt:
            sctx.toplevel_clause = sctx.clause
            sctx.expr_exposed = True

        if isinstance(result, qlast.Shape):
            result_expr = result.expr
            shape = result.elements
        else:
            result_expr = result
            shape = None

        if result_alias:
            stmtctx.declare_view(result_expr, alias=result_alias, ctx=sctx)
            result_expr = qlast.Path(
                steps=[qlast.ObjectRef(name=result_alias)]
            )

        expr = setgen.ensure_set(
            dispatch.compile(result_expr, ctx=sctx), ctx=sctx)

        result = compile_query_subject(
            expr, shape=shape, view_rptr=view_rptr, view_name=view_name,
            result_alias=result_alias,
            view_scls=view_scls,
            compile_views=ctx.stmt is ctx.toplevel_stmt,
            ctx=sctx)

        ctx.partial_path_prefix = result

    return result
Ejemplo n.º 9
0
    def visit_Argument(self, node, *, get_path_prefix):
        op = ast.ops.EQ
        name_parts = node.name

        _, target = self._get_parent_and_current_type()

        name = get_path_prefix()
        name.append(qlast.Ptr(ptr=qlast.ObjectRef(name=name_parts)))
        name = qlast.Path(steps=name)

        value = self.visit(node.value)

        # potentially need to cast the 'name' side into a <str>, so as
        # to be compatible with the 'value'
        typename = target.get_field_type(name_parts).short_name
        if (typename != 'str' and gt.EDB_TO_GQL_SCALARS_MAP[typename]
                in {GraphQLString, GraphQLID}):
            name = qlast.TypeCast(
                expr=name,
                type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')),
            )

        return qlast.BinOp(left=name, op=op, right=value)
Ejemplo n.º 10
0
 def reduce_DUNDERSOURCE(self, *kids):
     self.val = qlast.Path(steps=[qlast.Source()])
Ejemplo n.º 11
0
def _process_view(*,
                  scls: s_nodes.Node,
                  path_id: irast.PathId,
                  elements: typing.List[qlast.ShapeElement],
                  view_rptr: typing.Optional[context.ViewRPtr] = None,
                  view_name: typing.Optional[sn.SchemaName] = None,
                  is_insert: bool = False,
                  is_update: bool = False,
                  ctx: context.CompilerContext) -> s_nodes.Node:
    view_scls = schemactx.derive_view(scls,
                                      is_insert=is_insert,
                                      is_update=is_update,
                                      derived_name=view_name,
                                      ctx=ctx)
    is_mutation = is_insert or is_update
    is_defining_shape = ctx.expr_exposed or is_mutation

    pointers = []

    for shape_el in elements:
        with ctx.newscope(fenced=True) as scopectx:
            scopectx.singletons = ctx.singletons.copy()
            scopectx.singletons.add(path_id)
            pointers.append(
                _normalize_view_ptr_expr(shape_el,
                                         view_scls,
                                         path_id=path_id,
                                         is_insert=is_insert,
                                         is_update=is_update,
                                         view_rptr=view_rptr,
                                         ctx=scopectx))

    if is_insert:
        explicit_ptrs = {ptrcls.shortname for ptrcls in pointers}

        for pn, ptrcls in scls.pointers.items():
            if (not ptrcls.default or pn in explicit_ptrs
                    or ptrcls.is_pure_computable()):
                continue

            default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[
                qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls.shortname.name,
                                              module=ptrcls.shortname.module))
            ]))

            with ctx.newscope(fenced=True) as scopectx:
                scopectx.singletons = ctx.singletons.copy()
                scopectx.singletons.add(path_id)
                pointers.append(
                    _normalize_view_ptr_expr(default_ql,
                                             view_scls,
                                             path_id=path_id,
                                             is_insert=is_insert,
                                             is_update=is_update,
                                             view_rptr=view_rptr,
                                             ctx=scopectx))

    # Check if the view shape includes _only_ the link properties.
    # If so, we do not need to derive a new target view.
    lprops_only = True
    for ptrcls in pointers:
        if not ptrcls.is_link_property():
            lprops_only = False
            break

    if lprops_only:
        view_scls = scls

    for ptrcls in pointers:
        if ptrcls.is_link_property():
            source = view_rptr.derived_ptrcls
        else:
            source = view_scls

        if ptrcls.source is source and isinstance(source, s_sources.Source):
            # source may be an ScalarType in shapes that reference __type__,
            # hence the isinstance check.
            source.add_pointer(ptrcls, replace=True)

        if is_defining_shape:
            if source is None:
                # The nested shape is merely selecting the pointer,
                # so the link class has not been derived.  But for
                # the purposes of shape tracking, we must derive it
                # still.
                source = schemactx.derive_view(view_rptr.ptrcls,
                                               view_rptr.source,
                                               view_scls,
                                               is_insert=view_rptr.is_insert,
                                               is_update=view_rptr.is_update,
                                               ctx=ctx)
                view_rptr.derived_ptrcls = source

            ctx.class_shapes[source].append(ptrcls)

    return view_scls
Ejemplo n.º 12
0
    def visit_Field(self, node):
        if self._is_duplicate_field(node):
            return

        is_top, path, prevt, target, steps = \
            self._prepare_field(node)

        json_mode = False

        # determine if there needs to be extra subqueries
        if not prevt.dummy and target.dummy:
            json_mode = True

            # this is a special introspection type
            eql, shape, filterable = target.get_template()

            spec = qlast.ShapeElement(
                expr=qlast.Path(steps=[
                    qlast.Ptr(ptr=qlast.ObjectRef(
                        name=node.alias or node.name))
                ]),
                compexpr=eql,
            )

        elif prevt.is_field_shadowed(node.name):
            if prevt.has_native_field(node.name) and not node.alias:
                spec = filterable = shape = qlast.ShapeElement(
                    expr=qlast.Path(steps=steps), )
            else:
                prefix = qlast.Path(steps=self.get_path_prefix(-1))
                eql, shape, filterable = prevt.get_field_template(
                    node.name,
                    parent=prefix,
                    has_shape=bool(node.selection_set))
                spec = qlast.ShapeElement(
                    expr=qlast.Path(steps=[
                        qlast.Ptr(ptr=qlast.ObjectRef(
                            # this is already a sub-query
                            name=node.alias or node.name))
                    ]),
                    compexpr=eql)

        else:
            # if the parent is NOT a shadowed type, we need an explicit SELECT
            eql, shape, filterable = target.get_template()
            spec = qlast.ShapeElement(
                expr=qlast.Path(steps=[
                    qlast.Ptr(ptr=qlast.ObjectRef(
                        # this is already a sub-query
                        name=node.alias or node.name))
                ]),
                compexpr=eql)

        if node.selection_set is not None:
            if json_mode:
                pass

            else:
                # a single recursion target, so we can process
                # selection set now
                self._context.fields.append({})
                vals = self.visit(node.selection_set)
                self._context.fields.pop()

                if shape:
                    shape.elements = vals
                if filterable:
                    filterable.where = self._visit_path_where(node.arguments)

        path.pop()
        return spec
Ejemplo n.º 13
0
 def reduce_Identifier(self, *kids):
     self.val = qlast.Path(steps=[qlast.ObjectRef(name=kids[0].val)])
Ejemplo n.º 14
0
 def reduce_DOT_FCONST(self, *kids):
     # this is a valid link-like syntax for accessing unnamed tuples
     self.val = qlast.Path(steps=self._float_to_path(
         kids[1], kids[0].context),
                           partial=True)
Ejemplo n.º 15
0
 def reduce_PathStep(self, *kids):
     self.val = qlast.Path(steps=[kids[0].val], partial=True)
Ejemplo n.º 16
0
 def reduce_AT_ShortNodeName(self, *kids):
     self.val = qlast.Path(
         steps=[qlast.Ptr(ptr=kids[1].val, type='property')])
Ejemplo n.º 17
0
 def reduce_NodeName(self, *kids):
     self.val = qlast.Path(steps=[
         qlast.ObjectRef(name=kids[0].val.name, module=kids[0].val.module)
     ])
Ejemplo n.º 18
0
 def reduce_DUNDERSUBJECT(self, *kids):
     self.val = qlast.Path(steps=[qlast.Subject()])
Ejemplo n.º 19
0
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)
            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

        ptr_cardinality = ptrcls.cardinality

        if shape_el.elements:
            sub_view_rptr = context.ViewRPtr(view_scls,
                                             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)
        except errors.EdgeQLReferenceError:
            if is_mutation:
                raise

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

            if is_linkprop:
                ptr_metacls = s_props.Property
            else:
                ptr_metacls = s_links.Link

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

        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(view_scls,
                                                        ptrcls,
                                                        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
            derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls
            if derived_ptrcls is not None:
                ptrcls_is_derived = True
                ptrcls = derived_ptrcls

        inferred_cardinality = pathctx.infer_cardinality(irexpr, ctx=ctx)
        if inferred_cardinality == irast.Cardinality.MANY:
            ptr_cardinality = s_pointers.PointerCardinality.ManyToMany
        else:
            ptr_cardinality = s_pointers.PointerCardinality.ManyToOne

        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 is_mutation and base_ptrcls.singular():
            pathctx.enforce_singleton(irexpr, ctx=ctx)

    if qlexpr is not None or ptr_target is not ptrcls.target:
        if not ptrcls_is_derived:
            if ptrcls.is_link_property():
                rptrcls = view_rptr.derived_ptrcls
                if rptrcls is None:
                    rptrcls = schemactx.derive_view(
                        view_rptr.ptrcls,
                        view_rptr.source,
                        view_scls,
                        is_insert=view_rptr.is_insert,
                        is_update=view_rptr.is_update,
                        ctx=ctx)
                    view_rptr.derived_ptrcls = rptrcls

                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:
        ptrcls.cardinality = ptr_cardinality

    if ptrcls.shortname == 'std::__type__' and qlexpr is not None:
        msg = 'cannot assign to __type__'
        raise errors.EdgeQLError(msg, context=shape_el.context)

    return ptrcls