示例#1
0
    def _float_to_path(self, token, context):
        from edb.schema import pointers as s_pointers

        # make sure that the float is of the type 0.1
        parts = token.val.split('.')
        if not (len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit()):
            raise EdgeQLSyntaxError(f"Unexpected {token.val!r}",
                                    context=token.context)

        # context for the AST is established manually here
        return [
            qlast.Ptr(
                ptr=qlast.ObjectRef(
                    name=parts[0],
                    context=token.context,
                ),
                direction=s_pointers.PointerDirection.Outbound,
                context=context,
            ),
            qlast.Ptr(
                ptr=qlast.ObjectRef(
                    name=parts[1],
                    context=token.context,
                ),
                direction=s_pointers.PointerDirection.Outbound,
                context=token.context,
            )
        ]
示例#2
0
def _inject_tname(
        insert_stmt: qlast.InsertQuery, *,
        ctx: context.ContextLevel) -> None:

    for el in insert_stmt.shape:
        if isinstance(el.compexpr, qlast.InsertQuery):
            _inject_tname(el.compexpr, ctx=ctx)

    assert isinstance(insert_stmt.subject.steps[0], qlast.BaseObjectRef)
    insert_stmt.shape.append(
        qlast.ShapeElement(
            expr=qlast.Path(
                steps=[qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'))],
            ),
            compexpr=qlast.Path(
                steps=[
                    qlast.Introspect(
                        type=qlast.TypeName(
                            maintype=insert_stmt.subject.steps[0],
                        ),
                    ),
                    qlast.Ptr(ptr=qlast.ObjectRef(name='name')),
                ],
            ),
        ),
    )
示例#3
0
    def visit_order(self, node):
        if not isinstance(node, gql_ast.ObjectValue):
            raise g_errors.GraphQLTranslationError(
                f'an object is expected for "order"')

        # if there is no specific ordering, then order by id
        if not node.fields:
            return [
                qlast.SortExpr(
                    path=qlast.Path(
                        steps=[qlast.Ptr(ptr=qlast.ObjectRef(name='id'))],
                        partial=True,
                    ),
                    direction=qlast.SortAsc,
                )
            ]

        # Ordering is handled by specifying a list of special Ordering objects.
        # Validation is already handled by this point.
        orderby = []
        for enum in node.fields:
            name, direction, nulls = self._visit_order_item(enum)
            orderby.append(
                qlast.SortExpr(
                    path=qlast.Path(
                        steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))],
                        partial=True,
                    ),
                    direction=direction,
                    nones_order=nulls,
                ))

        return orderby
示例#4
0
def get_config_type_shape(
    schema: s_schema.Schema,
    stype: s_objtypes.ObjectType,
    path: List[qlast.Base],
) -> List[qlast.ShapeElement]:
    from . import objtypes as s_objtypes
    shape = []
    seen: Set[str] = set()

    stypes = [stype] + list(stype.descendants(schema))

    for t in stypes:
        t_name = t.get_name(schema)

        for unqual_pn, p in t.get_pointers(schema).items(schema):
            pn = str(unqual_pn)
            if pn in ('id', '__type__') or pn in seen:
                continue

            elem_path: List[qlast.Base] = []

            if t != stype:
                elem_path.append(
                    qlast.TypeIntersection(type=qlast.TypeName(
                        maintype=qlast.ObjectRef(
                            module=t_name.module,
                            name=t_name.name,
                        ), ), ), )

            elem_path.append(qlast.Ptr(ptr=qlast.ObjectRef(name=pn)))

            ptype = p.get_target(schema)
            assert ptype is not None

            if isinstance(ptype, s_objtypes.ObjectType):
                subshape = get_config_type_shape(schema, ptype,
                                                 path + elem_path)
                subshape.append(
                    qlast.ShapeElement(
                        expr=qlast.Path(steps=[
                            qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'), ),
                        ], ),
                        compexpr=qlast.Path(steps=path + elem_path + [
                            qlast.Ptr(ptr=qlast.ObjectRef(name='__type__')),
                            qlast.Ptr(ptr=qlast.ObjectRef(name='name')),
                        ], ),
                    ), )
            else:
                subshape = []

            shape.append(
                qlast.ShapeElement(
                    expr=qlast.Path(steps=elem_path),
                    elements=subshape,
                ), )

            seen.add(pn)

    return shape
示例#5
0
文件: viewgen.py 项目: sbdchd/edgedb
def _inline_type_computable(
    ir_set: irast.Set,
    stype: s_objtypes.ObjectType,
    compname: str,
    propname: str,
    *,
    shape_ptrs: List[ShapePtr],
    ctx: context.ContextLevel,
) -> None:
    assert isinstance(stype, s_objtypes.ObjectType)

    ptr: Optional[s_pointers.Pointer]
    try:
        ptr = setgen.resolve_ptr(stype, compname, track_ref=None, ctx=ctx)
        # The pointer might exist on the base type. That doesn't count,
        # and we need to re-inject it.
        if not ptr.get_computable(ctx.env.schema):
            ptr = None
    except errors.InvalidReferenceError:
        ptr = None

    if ptr is None:
        ql = qlast.ShapeElement(
            expr=qlast.Path(
                steps=[qlast.Ptr(
                    ptr=qlast.ObjectRef(name=compname),
                    direction=s_pointers.PointerDirection.Outbound,
                )],
            ),
            compexpr=qlast.Path(
                steps=[
                    qlast.Source(),
                    qlast.Ptr(
                        ptr=qlast.ObjectRef(name='__type__'),
                        direction=s_pointers.PointerDirection.Outbound,
                    ),
                    qlast.Ptr(
                        ptr=qlast.ObjectRef(name=propname),
                        direction=s_pointers.PointerDirection.Outbound,
                    )
                ]
            )
        )
        with ctx.newscope(fenced=True) as scopectx:
            scopectx.anchors = scopectx.anchors.copy()
            scopectx.anchors[qlast.Source().name] = ir_set
            ptr = _normalize_view_ptr_expr(
                ql, stype, path_id=ir_set.path_id, ctx=scopectx)

    view_shape = ctx.env.view_shapes[stype]
    view_shape_ptrs = {p for p, _ in view_shape}
    if ptr not in view_shape_ptrs:
        view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN))
        shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN))
示例#6
0
def get_config_type_shape(schema, stype,
                          path) -> typing.List[qlast.ShapeElement]:
    shape = []
    seen = set()

    stypes = [stype] + list(stype.descendants(schema))

    for t in stypes:
        t_name = t.get_name(schema)

        for pn, p in t.get_pointers(schema).items(schema):
            if pn in ('id', '__type__') or pn in seen:
                continue

            elem_path = []

            if t is not stype:
                elem_path.append(
                    qlast.TypeIndirection(type=qlast.TypeName(
                        maintype=qlast.ObjectRef(
                            module=t_name.module,
                            name=t_name.name,
                        ), ), ), )

            elem_path.append(qlast.Ptr(ptr=qlast.ObjectRef(name=pn)))

            ptype = p.get_target(schema)

            if ptype.is_object_type():
                subshape = get_config_type_shape(schema, ptype,
                                                 path + elem_path)
                subshape.append(
                    qlast.ShapeElement(
                        expr=qlast.Path(steps=[
                            qlast.Ptr(ptr=qlast.ObjectRef(name='_tname'), ),
                        ], ),
                        compexpr=qlast.Path(steps=path + elem_path + [
                            qlast.Ptr(ptr=qlast.ObjectRef(name='__type__')),
                            qlast.Ptr(ptr=qlast.ObjectRef(name='name')),
                        ], ),
                    ), )
            else:
                subshape = []

            shape.append(
                qlast.ShapeElement(
                    expr=qlast.Path(steps=elem_path),
                    elements=subshape,
                ), )

            seen.add(pn)

    return shape
示例#7
0
    def _prepare_field(self, node):
        path = self._context.path[-1]
        include_base = self._context.include_base[-1]

        is_top = self._is_top_level_field(node)

        spath = self._context.path[-1]
        prevt, target = self._get_parent_and_current_type()

        # insert normal or specialized link
        steps = []
        if include_base:
            base = spath[0].type
            steps.append(qlast.TypeIndirection(
                type=qlast.TypeName(
                    maintype=qlast.ObjectRef(
                        module=base.module,
                        name=base.short_name
                    ),
                ),
            ))
        steps.append(qlast.Ptr(
            ptr=qlast.ObjectRef(
                name=node.name.value
            )
        ))

        return is_top, path, prevt, target, steps
示例#8
0
    def get_path_prefix(self, end_trim=None):
        # flatten the path
        path = [step for psteps in self._context.path for step in psteps]

        # find the first shadowed root
        prev_base = None
        for i, step in enumerate(path):
            base = step.type

            # if the field is specifically shadowed, then this is
            # appropriate shadow base
            if (prev_base is not None
                    and prev_base.is_field_shadowed(step.name)):
                base = prev_base
                break

            # otherwise the base must be shadowing an entire type
            elif isinstance(base, gt.GQLShadowType):
                break

            prev_base = base

        # trim the rest of the path
        path = path[i + 1:end_trim]
        prefix = [qlast.ObjectRef(module=base.module, name=base.short_name)]
        prefix.extend(
            qlast.Ptr(ptr=qlast.ObjectRef(name=step.name)) for step in path)

        return prefix
示例#9
0
    def reduce_PathStepName(self, *kids):
        from edb.schema import pointers as s_pointers

        self.val = qlast.Path(steps=[
            qlast.Ptr(ptr=kids[0].val,
                      direction=s_pointers.PointerDirection.Outbound)
        ])
示例#10
0
    def reduce_DOTBW_PathStepName(self, *kids):
        from edb.schema import pointers as s_pointers

        self.val = qlast.Ptr(
            ptr=kids[1].val,
            direction=s_pointers.PointerDirection.Inbound
        )
示例#11
0
    def reduce_ShapePathPtr_DOT_PathStepName(self, *kids):
        from edb.schema import pointers as s_pointers

        self.val = qlast.Path(steps=[
            qlast.TypeIndirection(type=kids[0].val, ),
            qlast.Ptr(ptr=kids[2].val,
                      direction=s_pointers.PointerDirection.Outbound),
        ])
示例#12
0
    def reduce_AT_ShortNodeName(self, *kids):
        from edb.schema import pointers as s_pointers

        self.val = qlast.Ptr(
            ptr=kids[1].val,
            direction=s_pointers.PointerDirection.Outbound,
            type='property'
        )
示例#13
0
    def reduce_DOT_ICONST(self, *kids):
        # this is a valid link-like syntax for accessing unnamed tuples
        from edb.schema import pointers as s_pointers

        self.val = qlast.Ptr(
            ptr=qlast.ObjectRef(name=kids[1].val),
            direction=s_pointers.PointerDirection.Outbound
        )
示例#14
0
 def reduce_AT_ShortNodeName(self, *kids):
     self.val = qlast.Path(
         steps=[
             qlast.Ptr(
                 ptr=kids[1].val,
                 type='property'
             )
         ]
     )
示例#15
0
def extend_path(expr: qlast.Expr, field: str) -> qlast.Path:
    step = qlast.Ptr(ptr=qlast.ObjectRef(name=field))

    if isinstance(expr, qlast.Path):
        return qlast.Path(
            steps=[*expr.steps, step],
            partial=expr.partial,
        )
    else:
        return qlast.Path(steps=[expr, step])
示例#16
0
    def visit_ObjectField(self, node):
        fname = node.name.value

        # handle boolean ops
        if fname == 'and':
            return self._visit_list_of_inputs(node.value, 'AND')
        elif fname == 'or':
            return self._visit_list_of_inputs(node.value, 'OR')
        elif fname == 'not':
            return qlast.UnaryOp(op='NOT', operand=self.visit(node.value))

        # handle various scalar ops
        op = gt.GQL_TO_OPS_MAP.get(fname)

        if op:
            value = self.visit(node.value)
            return qlast.BinOp(left=self._context.filter, op=op, right=value)

        # we're at the beginning of a scalar op
        _, target = self._get_parent_and_current_type()

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

        ftype = target.get_field_type(fname)
        typename = ftype.name
        if typename not in {'std::str', 'std::uuid'}:
            gql_type = gt.EDB_TO_GQL_SCALARS_MAP.get(typename)
            if gql_type == graphql.GraphQLString:
                # potentially need to cast the 'name' side into a
                # <str>, so as to be compatible with the 'value'
                name = qlast.TypeCast(
                    expr=name,
                    type=qlast.TypeName(maintype=qlast.ObjectRef(name='str')),
                )

        self._context.filter = name

        value = self.visit(node.value)
        # we need to cast a target string into <uuid> or enum
        if typename == 'std::uuid' and not isinstance(value.right,
                                                      qlast.TypeCast):
            value.right = qlast.TypeCast(
                expr=value.right,
                type=qlast.TypeName(maintype=qlast.ObjectRef(name='uuid')),
            )
        elif ftype.is_enum():
            value.right = qlast.TypeCast(
                expr=value.right,
                type=qlast.TypeName(maintype=qlast.ObjectRef(name=ftype.name)),
            )

        return value
示例#17
0
def make_free_object(els: Dict[str, qlast.Expr]) -> qlast.Shape:
    return qlast.Shape(
        expr=None,
        elements=[
            qlast.ShapeElement(
                expr=qlast.Path(
                    steps=[qlast.Ptr(ptr=qlast.ObjectRef(name=name))]),
                compexpr=expr
            )
            for name, expr in els.items()
        ],
    )
示例#18
0
    def reduce_PathStepName_OptTypeIntersection(self, *kids):
        from edb.schema import pointers as s_pointers

        steps = [
            qlast.Ptr(ptr=kids[0].val,
                      direction=s_pointers.PointerDirection.Outbound),
        ]

        if kids[1].val is not None:
            steps.append(kids[1].val)

        self.val = qlast.Path(steps=steps)
示例#19
0
    def _apply_field_ast(
        self,
        schema: s_schema.Schema,
        context: sd.CommandContext,
        node: qlast.DDLOperation,
        op: sd.AlterObjectProperty,
    ) -> None:
        objtype = self.get_referrer_context(context)

        if op.property == 'target' and objtype:
            # Due to how SDL is processed the underlying AST may be an
            # AlterConcreteLink, which requires different handling.
            if isinstance(node, qlast.CreateConcreteLink):
                if not node.target:
                    expr = self.get_attribute_value('expr')
                    if expr is not None:
                        node.target = expr.qlast
                    else:
                        t = op.new_value
                        assert isinstance(t, (so.Object, so.ObjectShell))
                        node.target = utils.typeref_to_ast(schema, t)
            else:
                assert isinstance(op.new_value, (so.Object, so.ObjectShell))
                top_op = self.get_top_referrer_op(context)
                conv_expr: Optional[qlast.Expr]
                if (top_op is not None and
                    (isinstance(top_op, sd.CreateObject) or
                     (top_op.orig_cmd_type is not None
                      and issubclass(top_op.orig_cmd_type, sd.CreateObject)))):
                    # This op is part of CREATE TYPE, so avoid tripping up
                    # the DDL check on SET TYPE by generating an appropriate
                    # conversion expression: USING (.foo[IS Type]).
                    lname = sn.shortname_from_fullname(self.classname).name
                    conv_expr = qlast.Path(
                        partial=True,
                        steps=[
                            qlast.Ptr(ptr=qlast.ObjectRef(name=lname)),
                            qlast.TypeIntersection(type=utils.typeref_to_ast(
                                schema, op.new_value))
                        ],
                    )
                else:
                    conv_expr = None
                node.commands.append(
                    qlast.SetPointerType(
                        value=utils.typeref_to_ast(schema, op.new_value),
                        expr=conv_expr,
                    ))
        elif op.property == 'on_target_delete':
            node.commands.append(qlast.OnTargetDelete(cascade=op.new_value))
        else:
            super()._apply_field_ast(schema, context, node, op)
示例#20
0
    def _visit_path(self, node):
        steps = []

        while node.rptr:
            if node.show_as_anchor and not self.context.inline_anchors:
                break
            if isinstance(node.rptr.ptrref, irast.TypeIndirectionPointerRef):
                ttype = irtyputils.ir_typeref_to_type(self.context.schema,
                                                      node.typeref)
                steps.append(
                    qlast.TypeIndirection(type=typegen.type_to_ql_typeref(
                        ttype, ctx=self.context)))
            else:
                rptr = node.rptr
                ptrref = rptr.ptrref
                pname = ptrref.shortname

                link = qlast.Ptr(
                    ptr=qlast.ObjectRef(name=pname.name, ),
                    direction=rptr.direction,
                )
                if ptrref.parent_ptr is not None:
                    link.type = 'property'
                steps.append(link)

            node = node.rptr.source

        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:
            if node.typeref.material_type is not None:
                typeref = node.typeref.material_type
            else:
                typeref = node.typeref

            stype = self.context.schema.get_by_id(typeref.id)
            scls_shortname = stype.get_shortname(self.context.schema)
            step = qlast.ObjectRef(name=scls_shortname.name,
                                   module=scls_shortname.module)

        return qlast.Path(steps=[step] + list(reversed(steps)))
示例#21
0
    def visit_Set(self, node):
        if node.expr is not None:
            result = self.visit(node.expr)
        else:
            result = self._visit_path(node)

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

            for el in node.shape:
                rptr = el.rptr
                ptrref = rptr.ptrref
                pn = ptrref.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
示例#22
0
def computable_ptr_set(
    rptr: irast.Pointer,
    *,
    unnest_fence: bool = False,
    same_computable_scope: bool = False,
    srcctx: Optional[parsing.ParserContext] = None,
    ctx: context.ContextLevel,
) -> irast.Set:
    """Return ir.Set for a pointer defined as a computable."""
    ptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx)
    source_set = rptr.source
    source_scls = get_set_type(source_set, ctx=ctx)
    # process_view() may generate computable pointer expressions
    # in the form "self.linkname".  To prevent infinite recursion,
    # self must resolve to the parent type of the view NOT the view
    # type itself.  Similarly, when resolving computable link properties
    # make sure that we use the parent of derived ptrcls.
    if source_scls.is_view(ctx.env.schema):
        source_set_stype = source_scls.peel_view(ctx.env.schema)
        source_set = new_set_from_set(source_set,
                                      stype=source_set_stype,
                                      preserve_scope_ns=True,
                                      ctx=ctx)
        source_set.shape = []
        if source_set.rptr is not None:
            source_rptrref = source_set.rptr.ptrref
            if source_rptrref.base_ptr is not None:
                source_rptrref = source_rptrref.base_ptr
            source_set.rptr = irast.Pointer(
                source=source_set.rptr.source,
                target=source_set,
                ptrref=source_rptrref,
                direction=source_set.rptr.direction,
            )

    qlctx: Optional[context.ContextLevel]
    inner_source_path_id: Optional[irast.PathId]

    try:
        comp_info = ctx.source_map[ptrcls]
        qlexpr = comp_info.qlexpr
        assert isinstance(comp_info.context, context.ContextLevel)
        qlctx = comp_info.context
        inner_source_path_id = comp_info.path_id
        path_id_ns = comp_info.path_id_ns
    except KeyError:
        comp_expr = ptrcls.get_expr(ctx.env.schema)
        schema_qlexpr: Optional[qlast.Expr] = None
        if comp_expr is None and ctx.env.options.apply_query_rewrites:
            schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema)
            if schema_deflt is not None:
                assert isinstance(ptrcls, s_pointers.Pointer)
                ptrcls_n = ptrcls.get_shortname(ctx.env.schema).name
                schema_qlexpr = qlast.BinOp(
                    left=qlast.Path(steps=[
                        qlast.Source(),
                        qlast.Ptr(
                            ptr=qlast.ObjectRef(name=ptrcls_n),
                            direction=s_pointers.PointerDirection.Outbound,
                            type=('property' if ptrcls.is_link_property(
                                ctx.env.schema) else None))
                    ], ),
                    right=qlparser.parse_fragment(schema_deflt),
                    op='??',
                )

        if schema_qlexpr is None:
            if comp_expr is None:
                ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
                raise errors.InternalServerError(
                    f'{ptrcls_sn!r} is not a computable pointer')

            comp_qlexpr = qlparser.parse(comp_expr.text)
            assert isinstance(comp_qlexpr, qlast.Expr), 'expected qlast.Expr'
            schema_qlexpr = comp_qlexpr

        # NOTE: Validation of the expression type is not the concern
        # of this function. For any non-object pointer target type,
        # the default expression must be assignment-cast into that
        # type.
        target_scls = ptrcls.get_target(ctx.env.schema)
        assert target_scls is not None
        if not target_scls.is_object_type():
            schema_qlexpr = qlast.TypeCast(
                type=typegen.type_to_ql_typeref(target_scls, ctx=ctx),
                expr=schema_qlexpr,
            )
        qlexpr = astutils.ensure_qlstmt(schema_qlexpr)
        qlctx = None
        inner_source_path_id = None
        path_id_ns = None

    newctx: Callable[[], ContextManager[context.ContextLevel]]

    if qlctx is None:
        # Schema-level computable, completely detached context
        newctx = ctx.detached
    else:
        newctx = _get_computable_ctx(rptr=rptr,
                                     source=source_set,
                                     source_scls=source_scls,
                                     inner_source_path_id=inner_source_path_id,
                                     path_id_ns=path_id_ns,
                                     same_scope=same_computable_scope,
                                     qlctx=qlctx,
                                     ctx=ctx)

    if ptrcls.is_link_property(ctx.env.schema):
        source_path_id = rptr.source.path_id.ptr_path()
    else:
        src_path = rptr.target.path_id.src_path()
        assert src_path is not None
        source_path_id = src_path

    result_path_id = pathctx.extend_path_id(
        source_path_id,
        ptrcls=ptrcls,
        ns=ctx.path_id_namespace,
        ctx=ctx,
    )

    result_stype = ptrcls.get_target(ctx.env.schema)
    base_object = ctx.env.schema.get('std::BaseObject', type=s_types.Type)
    with newctx() as subctx:
        subctx.disable_shadowing.add(ptrcls)
        if result_stype != base_object:
            subctx.view_scls = result_stype
        subctx.view_rptr = context.ViewRPtr(source_scls,
                                            ptrcls=ptrcls,
                                            rptr=rptr)  # type: ignore
        subctx.anchors[qlast.Source().name] = source_set
        subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema)
        subctx.partial_path_prefix = source_set
        # On a mutation, make the expr_exposed. This corresponds with
        # a similar check on is_mutation in _normalize_view_ptr_expr.
        if (source_scls.get_expr_type(ctx.env.schema) !=
                s_types.ExprType.Select):
            subctx.expr_exposed = True

        if isinstance(qlexpr, qlast.Statement):
            subctx.stmt_metadata[qlexpr] = context.StatementMetadata(
                is_unnest_fence=unnest_fence,
                iterator_target=True,
            )

        comp_ir_set = ensure_set(dispatch.compile(qlexpr, ctx=subctx),
                                 ctx=subctx)

    comp_ir_set = new_set_from_set(comp_ir_set,
                                   path_id=result_path_id,
                                   rptr=rptr,
                                   context=srcctx,
                                   ctx=ctx)

    rptr.target = comp_ir_set

    return comp_ir_set
示例#23
0
文件: viewgen.py 项目: sbdchd/edgedb
def _process_view(
    *,
    stype: s_objtypes.ObjectType,
    path_id: irast.PathId,
    path_id_namespace: Optional[irast.WeakNamespace] = None,
    elements: List[qlast.ShapeElement],
    view_rptr: Optional[context.ViewRPtr] = None,
    view_name: Optional[sn.QualName] = None,
    is_insert: bool = False,
    is_update: bool = False,
    is_delete: bool = False,
    parser_context: pctx.ParserContext,
    ctx: context.ContextLevel,
) -> s_objtypes.ObjectType:

    if (view_name is None and ctx.env.options.schema_view_mode
            and view_rptr is not None):
        # Make sure persistent schema expression aliases have properly formed
        # names as opposed to the usual mangled form of the ephemeral
        # aliases.  This is needed for introspection readability, as well
        # as helps in maintaining proper type names for schema
        # representations that require alphanumeric names, such as
        # GraphQL.
        #
        # We use the name of the source together with the name
        # of the inbound link to form the name, so in e.g.
        #    CREATE ALIAS V := (SELECT Foo { bar: { baz: { ... } })
        # The name of the innermost alias would be "__V__bar__baz".
        source_name = view_rptr.source.get_name(ctx.env.schema).name
        if not source_name.startswith('__'):
            source_name = f'__{source_name}'
        if view_rptr.ptrcls_name is not None:
            ptr_name = view_rptr.ptrcls_name.name
        elif view_rptr.ptrcls is not None:
            ptr_name = view_rptr.ptrcls.get_shortname(ctx.env.schema).name
        else:
            raise errors.InternalServerError(
                '_process_view in schema mode received view_rptr with '
                'neither ptrcls_name, not ptrcls'
            )

        name = f'{source_name}__{ptr_name}'
        view_name = sn.QualName(
            module=ctx.derived_target_module or '__derived__',
            name=name,
        )

    view_scls = schemactx.derive_view(
        stype,
        is_insert=is_insert,
        is_update=is_update,
        is_delete=is_delete,
        derived_name=view_name,
        ctx=ctx,
    )
    assert isinstance(view_scls, s_objtypes.ObjectType), view_scls
    is_mutation = is_insert or is_update
    is_defining_shape = ctx.expr_exposed or is_mutation

    if view_rptr is not None and view_rptr.ptrcls is None:
        derive_ptrcls(
            view_rptr, target_scls=view_scls,
            transparent=True, ctx=ctx)

    pointers = []

    for shape_el in elements:
        with ctx.newscope(fenced=True) as scopectx:
            pointer = _normalize_view_ptr_expr(
                shape_el, view_scls, path_id=path_id,
                path_id_namespace=path_id_namespace,
                is_insert=is_insert, is_update=is_update,
                view_rptr=view_rptr,
                ctx=scopectx)

            if pointer in pointers:
                schema = ctx.env.schema
                vnp = pointer.get_verbosename(schema, with_parent=True)

                raise errors.QueryError(
                    f'duplicate definition of {vnp}',
                    context=shape_el.context)

            pointers.append(pointer)

    if is_insert:
        explicit_ptrs = {
            ptrcls.get_local_name(ctx.env.schema)
            for ptrcls in pointers
        }
        scls_pointers = stype.get_pointers(ctx.env.schema)
        for pn, ptrcls in scls_pointers.items(ctx.env.schema):
            if (pn in explicit_ptrs or
                    ptrcls.is_pure_computable(ctx.env.schema)):
                continue

            default_expr = ptrcls.get_default(ctx.env.schema)
            if not default_expr:
                if (
                    ptrcls.get_required(ctx.env.schema)
                    and pn != sn.UnqualName('__type__')
                ):
                    if ptrcls.is_property(ctx.env.schema):
                        # If the target is a sequence, there's no need
                        # for an explicit value.
                        ptrcls_target = ptrcls.get_target(ctx.env.schema)
                        assert ptrcls_target is not None
                        if ptrcls_target.issubclass(
                                ctx.env.schema,
                                ctx.env.schema.get(
                                    'std::sequence',
                                    type=s_objects.SubclassableObject)):
                            continue
                    vn = ptrcls.get_verbosename(
                        ctx.env.schema, with_parent=True)
                    raise errors.MissingRequiredError(
                        f'missing value for required {vn}')
                else:
                    continue

            ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
            default_ql = qlast.ShapeElement(
                expr=qlast.Path(
                    steps=[
                        qlast.Ptr(
                            ptr=qlast.ObjectRef(
                                name=ptrcls_sn.name,
                                module=ptrcls_sn.module,
                            ),
                        ),
                    ],
                ),
                compexpr=qlast.DetachedExpr(
                    expr=default_expr.qlast,
                ),
            )

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

    elif (
        stype.get_name(ctx.env.schema).module == 'schema'
        and ctx.env.options.apply_query_rewrites
    ):
        explicit_ptrs = {
            ptrcls.get_local_name(ctx.env.schema)
            for ptrcls in pointers
        }
        scls_pointers = stype.get_pointers(ctx.env.schema)
        for pn, ptrcls in scls_pointers.items(ctx.env.schema):
            if (
                pn in explicit_ptrs
                or ptrcls.is_pure_computable(ctx.env.schema)
            ):
                continue

            schema_deflt = ptrcls.get_schema_reflection_default(ctx.env.schema)
            if schema_deflt is None:
                continue

            with ctx.newscope(fenced=True) as scopectx:
                ptr_ref = s_utils.name_to_ast_ref(pn)
                implicit_ql = qlast.ShapeElement(
                    expr=qlast.Path(steps=[qlast.Ptr(ptr=ptr_ref)]),
                    compexpr=qlast.BinOp(
                        left=qlast.Path(
                            partial=True,
                            steps=[
                                qlast.Ptr(
                                    ptr=ptr_ref,
                                    direction=(
                                        s_pointers.PointerDirection.Outbound
                                    ),
                                )
                            ],
                        ),
                        right=qlparser.parse_fragment(schema_deflt),
                        op='??',
                    ),
                )

                # Note: we only need to record the schema default
                # as a computable, but not include it in the type
                # shape, so we ignore the return value.
                _normalize_view_ptr_expr(
                    implicit_ql,
                    view_scls,
                    path_id=path_id,
                    path_id_namespace=path_id_namespace,
                    is_insert=is_insert,
                    is_update=is_update,
                    view_rptr=view_rptr,
                    ctx=scopectx,
                )

    for ptrcls in pointers:
        source: Union[s_types.Type, s_pointers.PointerLike]

        if ptrcls.is_link_property(ctx.env.schema):
            assert view_rptr is not None and view_rptr.ptrcls is not None
            source = view_rptr.ptrcls
        else:
            source = view_scls

        if is_defining_shape:
            cinfo = ctx.source_map.get(ptrcls)
            if cinfo is not None:
                shape_op = cinfo.shape_op
            else:
                shape_op = qlast.ShapeOp.ASSIGN

            ctx.env.view_shapes[source].append((ptrcls, shape_op))

    if (view_rptr is not None and view_rptr.ptrcls is not None and
            view_scls != stype):
        ctx.env.schema = view_scls.set_field_value(
            ctx.env.schema, 'rptr', view_rptr.ptrcls)

    return view_scls
示例#24
0
文件: viewgen.py 项目: zhutony/edgedb
def _get_shape_configuration(
    ir_set: irast.Set,
    *,
    rptr: Optional[irast.Pointer]=None,
    parent_view_type: Optional[s_types.ExprType]=None,
    ctx: context.ContextLevel
) -> List[Tuple[irast.Set, s_pointers.Pointer, qlast.ShapeOp]]:

    """Return a list of (source_set, ptrcls) pairs as a shape for a given set.
    """

    stype = setgen.get_set_type(ir_set, ctx=ctx)

    sources: List[
        Union[s_types.Type, s_pointers.PointerLike]] = []
    link_view = False
    is_objtype = ir_set.path_id.is_objtype_path()

    if rptr is None:
        rptr = ir_set.rptr

    if rptr is not None:
        rptrcls = typegen.ptrcls_from_ptrref(rptr.ptrref, ctx=ctx)
    else:
        rptrcls = None

    link_view = (
        rptrcls is not None and
        not rptrcls.is_link_property(ctx.env.schema) and
        _link_has_shape(rptrcls, ctx=ctx)
    )

    if is_objtype or not link_view:
        sources.append(stype)

    if link_view:
        sources.append(rptrcls)

    shape_ptrs = []

    for source in sources:
        for ptr, shape_op in ctx.env.view_shapes[source]:
            if (ptr.is_link_property(ctx.env.schema) and
                    ir_set.path_id != rptr.target.path_id):
                path_tip = rptr.target
            else:
                path_tip = ir_set

            shape_ptrs.append((path_tip, ptr, shape_op))

    if is_objtype:
        assert isinstance(stype, s_objtypes.ObjectType)

        view_type = stype.get_expr_type(ctx.env.schema)
        is_mutation = view_type in (s_types.ExprType.Insert,
                                    s_types.ExprType.Update)
        is_parent_update = parent_view_type is s_types.ExprType.Update

        implicit_id = (
            # shape is not specified at all
            not shape_ptrs
            # implicit ids are always wanted
            or (ctx.implicit_id_in_shapes and not is_mutation)
            # we are inside an UPDATE shape and this is
            # an explicit expression (link target update)
            or (is_parent_update and ir_set.expr is not None)
        )

        if implicit_id:
            # We want the id in this shape and it's not already there,
            # so insert it in the first position.
            pointers = stype.get_pointers(ctx.env.schema).objects(
                ctx.env.schema)
            view_shape = ctx.env.view_shapes[stype]
            view_shape_ptrs = {p for p, _ in view_shape}
            for ptr in pointers:
                if ptr.is_id_pointer(ctx.env.schema):
                    if ptr not in view_shape_ptrs:
                        shape_metadata = ctx.env.view_shapes_metadata[stype]
                        view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN))
                        shape_metadata.has_implicit_id = True
                        shape_ptrs.insert(
                            0, (ir_set, ptr, qlast.ShapeOp.ASSIGN))
                    break

    is_mutation = parent_view_type in {
        s_types.ExprType.Insert,
        s_types.ExprType.Update
    }

    implicit_tid = (
        stype is not None
        and has_implicit_tid(stype, is_mutation=is_mutation, ctx=ctx)
    )

    if implicit_tid:
        assert isinstance(stype, s_objtypes.ObjectType)

        try:
            ptr = setgen.resolve_ptr(stype, '__tid__', ctx=ctx)
        except errors.InvalidReferenceError:
            ql = qlast.ShapeElement(
                expr=qlast.Path(
                    steps=[qlast.Ptr(
                        ptr=qlast.ObjectRef(name='__tid__'),
                        direction=s_pointers.PointerDirection.Outbound,
                    )],
                ),
                compexpr=qlast.Path(
                    steps=[
                        qlast.Source(),
                        qlast.Ptr(
                            ptr=qlast.ObjectRef(name='__type__'),
                            direction=s_pointers.PointerDirection.Outbound,
                        ),
                        qlast.Ptr(
                            ptr=qlast.ObjectRef(name='id'),
                            direction=s_pointers.PointerDirection.Outbound,
                        )
                    ]
                )
            )
            with ctx.newscope(fenced=True) as scopectx:
                scopectx.anchors = scopectx.anchors.copy()
                scopectx.anchors[qlast.Source().name] = ir_set
                ptr = _normalize_view_ptr_expr(
                    ql, stype, path_id=ir_set.path_id, ctx=scopectx)

        view_shape = ctx.env.view_shapes[stype]
        view_shape_ptrs = {p for p, _ in view_shape}
        if ptr not in view_shape_ptrs:
            view_shape.insert(0, (ptr, qlast.ShapeOp.ASSIGN))
            shape_ptrs.insert(0, (ir_set, ptr, qlast.ShapeOp.ASSIGN))

    return shape_ptrs
示例#25
0
def _process_view(*,
                  stype: s_nodes.Node,
                  path_id: irast.PathId,
                  path_id_namespace: typing.Optional[
                      irast.WeakNamespace] = None,
                  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(stype,
                                      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

    if view_rptr is not None and view_rptr.ptrcls is None:
        derive_ptrcls(view_rptr,
                      target_scls=view_scls,
                      transparent=True,
                      ctx=ctx)

    pointers = []

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

    if is_insert:
        explicit_ptrs = {
            ptrcls.get_shortname(ctx.env.schema).name
            for ptrcls in pointers
        }

        scls_pointers = stype.get_pointers(ctx.env.schema)
        for pn, ptrcls in scls_pointers.items(ctx.env.schema):
            if (pn in explicit_ptrs
                    or ptrcls.is_pure_computable(ctx.env.schema)):
                continue

            if not ptrcls.get_default(ctx.env.schema):
                if ptrcls.get_required(ctx.env.schema):
                    if ptrcls.is_property(ctx.env.schema):
                        what = 'property'
                    else:
                        what = 'link'
                    raise errors.MissingRequiredError(
                        f'missing value for required {what} '
                        f'{stype.get_displayname(ctx.env.schema)}.'
                        f'{ptrcls.get_displayname(ctx.env.schema)}')
                else:
                    continue

            ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
            default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[
                qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls_sn.name,
                                              module=ptrcls_sn.module))
            ]))

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

    for ptrcls in pointers:
        if ptrcls.is_link_property(ctx.env.schema):
            source = view_rptr.ptrcls
        else:
            source = view_scls

        if is_defining_shape:
            ctx.env.view_shapes[source].append(ptrcls)

    if (view_rptr is not None and view_rptr.ptrcls is not None
            and view_scls is not stype):
        ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr',
                                                   view_rptr.ptrcls)

    return view_scls
示例#26
0
def _cast_array(ir_set: irast.Set, orig_stype: s_types.Type,
                new_stype: s_types.Type, *,
                srcctx: Optional[parsing.ParserContext],
                ctx: context.ContextLevel) -> irast.Set:

    assert isinstance(orig_stype, s_types.Array)

    direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx)

    if direct_cast is None:
        if not new_stype.is_array():
            raise errors.QueryError(
                f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} '
                f'to {new_stype.get_displayname(ctx.env.schema)!r}',
                context=srcctx)
        assert isinstance(new_stype, s_types.Array)
        el_type = new_stype.get_subtypes(ctx.env.schema)[0]
    else:
        el_type = new_stype

    orig_el_type = orig_stype.get_subtypes(ctx.env.schema)[0]

    el_cast = _find_cast(orig_el_type, el_type, srcctx=srcctx, ctx=ctx)

    if el_cast is not None and el_cast.get_from_cast(ctx.env.schema):
        # Simple cast
        return _cast_to_ir(ir_set, el_cast, orig_stype, new_stype, ctx=ctx)
    else:
        pathctx.register_set_in_scope(ir_set, ctx=ctx)

        with ctx.new() as subctx:
            subctx.anchors = subctx.anchors.copy()
            source_alias = subctx.aliases.get('a')
            subctx.anchors[source_alias] = ir_set

            unpacked = qlast.FunctionCall(
                func=('__std__', 'array_unpack'),
                args=[
                    qlast.Path(steps=[qlast.ObjectRef(name=source_alias)], ),
                ],
            )

            enumerated = setgen.ensure_set(
                dispatch.compile(
                    qlast.FunctionCall(
                        func=('__std__', 'enumerate'),
                        args=[unpacked],
                    ),
                    ctx=subctx,
                ),
                ctx=subctx,
            )

            enumerated_alias = subctx.aliases.get('e')
            subctx.anchors[enumerated_alias] = enumerated
            enumerated_ref = qlast.Path(
                steps=[qlast.ObjectRef(name=enumerated_alias)], )

            elements = qlast.FunctionCall(
                func=('__std__', 'array_agg'),
                args=[
                    qlast.SelectQuery(
                        result=qlast.TypeCast(
                            expr=qlast.Path(steps=[
                                enumerated_ref,
                                qlast.Ptr(ptr=qlast.ObjectRef(
                                    name='1',
                                    direction='>',
                                ), ),
                            ], ),
                            type=typegen.type_to_ql_typeref(
                                el_type,
                                ctx=subctx,
                            ),
                            cardinality_mod=qlast.CardinalityModifier.Required,
                        ),
                        orderby=[
                            qlast.SortExpr(
                                path=qlast.Path(steps=[
                                    enumerated_ref,
                                    qlast.Ptr(ptr=qlast.ObjectRef(
                                        name='0',
                                        direction='>',
                                    ), ),
                                ], ),
                                direction=qlast.SortOrder.Asc,
                            ),
                        ],
                    ),
                ],
            )

            array_ir = dispatch.compile(elements, ctx=subctx)
            assert isinstance(array_ir, irast.Set)

            if direct_cast is not None:
                ctx.env.schema, array_stype = s_types.Array.from_subtypes(
                    ctx.env.schema, [el_type])
                return _cast_to_ir(array_ir,
                                   direct_cast,
                                   array_stype,
                                   new_stype,
                                   ctx=ctx)
            else:
                return array_ir
示例#27
0
    # We use `any` to compute the disjunction here because some might
    # be empty.
    if len(conds) == 1:
        cond = conds[0]
    else:
        cond = qlast.FunctionCall(
            func='any',
            args=[qlast.Set(elements=conds)],
        )

    # For the result filtering we need to *ignore* the same object
    if fake_dml_set:
        anchor = qlutils.subject_paths_substitute(
            ptr_anchors['id'], ptr_anchors)
        ptr_val = qlast.Path(partial=True, steps=[
            qlast.Ptr(ptr=qlast.ObjectRef(name='id'))
        ])
        cond = qlast.BinOp(
            op='AND',
            left=cond,
            right=qlast.BinOp(op='!=', left=anchor, right=ptr_val),
        )

    # Produce a query that finds the conflicting objects
    select_ast = qlast.DetachedExpr(
        expr=qlast.SelectQuery(result=insert_subject, where=cond)
    )

    return select_ast

示例#28
0
def _process_view(*,
                  stype: s_nodes.Node,
                  path_id: irast.PathId,
                  path_id_namespace: typing.Optional[
                      irast.WeakNamespace] = None,
                  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(stype,
                                      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:
            pointers.append(
                _normalize_view_ptr_expr(shape_el,
                                         view_scls,
                                         path_id=path_id,
                                         path_id_namespace=path_id_namespace,
                                         is_insert=is_insert,
                                         is_update=is_update,
                                         view_rptr=view_rptr,
                                         ctx=scopectx))

    if is_insert:
        explicit_ptrs = {
            ptrcls.get_shortname(ctx.env.schema).name
            for ptrcls in pointers
        }

        scls_pointers = stype.get_pointers(ctx.env.schema)
        for pn, ptrcls in scls_pointers.items(ctx.env.schema):
            if (pn in explicit_ptrs
                    or ptrcls.is_pure_computable(ctx.env.schema)):
                continue

            if not ptrcls.get_default(ctx.env.schema):
                if ptrcls.get_required(ctx.env.schema):
                    if ptrcls.is_property(ctx.env.schema):
                        what = 'property'
                    else:
                        what = 'link'
                    raise errors.MissingRequiredError(
                        f'missing value for required {what} '
                        f'{stype.get_displayname(ctx.env.schema)}.'
                        f'{ptrcls.get_displayname(ctx.env.schema)}')
                else:
                    continue

            ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
            default_ql = qlast.ShapeElement(expr=qlast.Path(steps=[
                qlast.Ptr(ptr=qlast.ObjectRef(name=ptrcls_sn.name,
                                              module=ptrcls_sn.module))
            ]))

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

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

        if ptrcls.get_source(ctx.env.schema) is source:
            ctx.env.schema = source.add_pointer(ctx.env.schema,
                                                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.  The derived pointer must be treated the same
                # as the original, as this is not a new computable,
                # and both `Foo.ptr` and `Foo { ptr }` are the same path,
                # hence the `transparent` modifier.
                source = derive_ptrcls(view_rptr,
                                       target_scls=view_scls,
                                       transparent=True,
                                       ctx=ctx)

            ctx.env.view_shapes[source].append(ptrcls)

    if (view_rptr is not None and view_rptr.derived_ptrcls is not None
            and view_scls is not stype):
        ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr',
                                                   view_rptr.derived_ptrcls)

    return view_scls
示例#29
0
def _get_shape_configuration(
        ir_set: irast.Set, *,
        rptr: typing.Optional[irast.Pointer]=None,
        parent_view_type: typing.Optional[s_types.ViewType]=None,
        ctx: context.ContextLevel) \
        -> typing.List[typing.Tuple[irast.Set, s_pointers.Pointer]]:
    """Return a list of (source_set, ptrcls) pairs as a shape for a given set.
    """

    stype = setgen.get_set_type(ir_set, ctx=ctx)

    sources = []
    link_view = False
    is_objtype = ir_set.path_id.is_objtype_path()

    if rptr is None:
        rptr = ir_set.rptr

    if rptr is not None:
        rptrcls = irtyputils.ptrcls_from_ptrref(rptr.ptrref,
                                                schema=ctx.env.schema)
    else:
        rptrcls = None

    link_view = (rptrcls is not None
                 and not rptrcls.is_link_property(ctx.env.schema)
                 and _link_has_shape(rptrcls, ctx=ctx))

    if is_objtype or not link_view:
        sources.append(stype)

    if link_view:
        sources.append(rptrcls)

    shape_ptrs = []

    id_present_in_shape = False

    for source in sources:
        for ptr in ctx.env.view_shapes[source]:
            if (ptr.is_link_property(ctx.env.schema)
                    and ir_set.path_id != rptr.target.path_id):
                path_tip = rptr.target
            else:
                path_tip = ir_set

            shape_ptrs.append((path_tip, ptr))

            if source is stype and ptr.is_id_pointer(ctx.env.schema):
                id_present_in_shape = True

    if is_objtype and not id_present_in_shape:
        view_type = stype.get_view_type(ctx.env.schema)
        is_mutation = view_type in (s_types.ViewType.Insert,
                                    s_types.ViewType.Update)
        is_parent_update = parent_view_type is s_types.ViewType.Update

        implicit_id = (
            # shape is not specified at all
            not shape_ptrs
            # implicit ids are always wanted
            or (ctx.implicit_id_in_shapes and not is_mutation)
            # we are inside an UPDATE shape and this is
            # an explicit expression (link target update)
            or (is_parent_update and ir_set.expr is not None))

        if implicit_id:
            # We want the id in this shape and it's not already there,
            # so insert it in the first position.
            pointers = stype.get_pointers(ctx.env.schema).objects(
                ctx.env.schema)
            for ptr in pointers:
                if ptr.is_id_pointer(ctx.env.schema):
                    view_shape = ctx.env.view_shapes[stype]
                    if ptr not in view_shape:
                        shape_metadata = ctx.env.view_shapes_metadata[stype]
                        view_shape.insert(0, ptr)
                        shape_metadata.has_implicit_id = True
                        shape_ptrs.insert(0, (ir_set, ptr))
                    break

    if (ir_set.typeref is not None and irtyputils.is_object(ir_set.typeref)
            and parent_view_type is not s_types.ViewType.Insert
            and parent_view_type is not s_types.ViewType.Update
            and ctx.implicit_tid_in_shapes):
        ql = qlast.ShapeElement(
            expr=qlast.Path(steps=[
                qlast.Ptr(
                    ptr=qlast.ObjectRef(name='__tid__'),
                    direction=s_pointers.PointerDirection.Outbound,
                )
            ], ),
            compexpr=qlast.Path(steps=[
                qlast.Source(),
                qlast.Ptr(
                    ptr=qlast.ObjectRef(name='__type__'),
                    direction=s_pointers.PointerDirection.Outbound,
                ),
                qlast.Ptr(
                    ptr=qlast.ObjectRef(name='id'),
                    direction=s_pointers.PointerDirection.Outbound,
                )
            ]))
        with ctx.newscope(fenced=True) as scopectx:
            scopectx.anchors = scopectx.anchors.copy()
            scopectx.anchors[qlast.Source] = ir_set
            ptr = _normalize_view_ptr_expr(ql,
                                           stype,
                                           path_id=ir_set.path_id,
                                           ctx=scopectx)
            view_shape = ctx.env.view_shapes[stype]
            if ptr not in view_shape:
                view_shape.insert(0, ptr)
                shape_ptrs.insert(0, (ir_set, ptr))

    return shape_ptrs
示例#30
0
def _compile_conflict_select(
    stmt: irast.MutatingStmt,
    subject_typ: s_objtypes.ObjectType,
    *,
    for_inheritance: bool,
    fake_dml_set: Optional[irast.Set],
    obj_constrs: Sequence[s_constr.Constraint],
    constrs: Dict[str, Tuple[s_pointers.Pointer, List[s_constr.Constraint]]],
    parser_context: Optional[pctx.ParserContext],
    ctx: context.ContextLevel,
) -> Optional[qlast.Expr]:
    """Synthesize a select of conflicting objects

    ... for a single object type. This gets called once for each ancestor
    type that provides constraints to the type being inserted.

    `cnstrs` contains the constraints to consider.
    """
    # Find which pointers we need to grab
    needed_ptrs, ptr_anchors = _get_needed_ptrs(
        subject_typ, obj_constrs, constrs.keys(), ctx=ctx
    )

    ctx.anchors = ctx.anchors.copy()

    # If we are given a fake_dml_set to directly represent the result
    # of our DML, use that instead of populating the result.
    if fake_dml_set:
        for p in needed_ptrs | {'id'}:
            ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p))
            val = setgen.extend_path(fake_dml_set, ptr, ctx=ctx)

            ptr_anchors[p] = ctx.create_anchor(val, p)

    # Find the IR corresponding to the fields we care about and
    # produce anchors for them
    ptrs_in_shape = set()
    for elem, _ in stmt.subject.shape:
        assert elem.rptr is not None
        name = elem.rptr.ptrref.shortname.name
        ptrs_in_shape.add(name)
        if name in needed_ptrs and name not in ptr_anchors:
            assert elem.expr
            if inference.infer_volatility(elem.expr, ctx.env).is_volatile():
                if for_inheritance:
                    error = (
                        'INSERT does not support volatile properties with '
                        'exclusive constraints when another statement in '
                        'the same query modifies a related type'
                    )
                else:
                    error = (
                        'INSERT UNLESS CONFLICT ON does not support volatile '
                        'properties'
                    )
                raise errors.UnsupportedFeatureError(
                    error, context=parser_context
                )

            # We want to use the same path_scope_id as the original
            elem_set = setgen.ensure_set(elem.expr, ctx=ctx)
            elem_set.path_scope_id = elem.path_scope_id

            # FIXME: The wrong thing will definitely happen if there are
            # volatile entries here
            ptr_anchors[name] = ctx.create_anchor(elem_set, name)

    if for_inheritance and not ptrs_in_shape:
        return None

    # Fill in empty sets for pointers that are needed but not present
    present_ptrs = set(ptr_anchors)
    for p in (needed_ptrs - present_ptrs):
        ptr = subject_typ.getptr(ctx.env.schema, s_name.UnqualName(p))
        typ = ptr.get_target(ctx.env.schema)
        assert typ
        ptr_anchors[p] = qlast.TypeCast(
            expr=qlast.Set(elements=[]),
            type=typegen.type_to_ql_typeref(typ, ctx=ctx))

    if not ptr_anchors:
        raise errors.QueryError(
            'INSERT UNLESS CONFLICT property requires matching shape',
            context=parser_context,
        )

    conds: List[qlast.Expr] = []
    for ptrname, (ptr, ptr_cnstrs) in constrs.items():
        if ptrname not in present_ptrs:
            continue
        anchor = qlutils.subject_paths_substitute(
            ptr_anchors[ptrname], ptr_anchors)
        ptr_val = qlast.Path(partial=True, steps=[
            qlast.Ptr(ptr=qlast.ObjectRef(name=ptrname))
        ])
        ptr, ptr_cnstrs = constrs[ptrname]
        ptr_card = ptr.get_cardinality(ctx.env.schema)

        for cnstr in ptr_cnstrs:
            lhs: qlast.Expr = anchor
            rhs: qlast.Expr = ptr_val
            # If there is a subjectexpr, substitute our lhs and rhs in
            # for __subject__ in the subjectexpr and compare *that*
            if (subjectexpr := cnstr.get_subjectexpr(ctx.env.schema)):
                assert isinstance(subjectexpr.qlast, qlast.Expr)
                lhs = qlutils.subject_substitute(subjectexpr.qlast, lhs)
                rhs = qlutils.subject_substitute(subjectexpr.qlast, rhs)

            conds.append(qlast.BinOp(
                op='=' if ptr_card.is_single() else 'IN',
                left=lhs, right=rhs,
            ))