コード例 #1
0
ファイル: expressions.py プロジェクト: isidentical/edgedb
 def reduce_Expr_CIRCUMFLEX_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op=kids[1].val,
                            right=kids[2].val)
コード例 #2
0
ファイル: expressions.py プロジェクト: isidentical/edgedb
 def reduce_Expr_DOUBLESLASH_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op=kids[1].val,
                            right=kids[2].val)
コード例 #3
0
ファイル: expressions.py プロジェクト: isidentical/edgedb
 def reduce_Expr_PERCENT_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op=kids[1].val,
                            right=kids[2].val)
コード例 #4
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_UNION_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val, op='UNION', right=kids[2].val)
コード例 #5
0
def compile_insert_unless_conflict(
    stmt: irast.InsertStmt,
    insert_subject: qlast.Path,
    constraint_spec: qlast.Expr,
    else_branch: Optional[qlast.Expr],
    *,
    ctx: context.ContextLevel,
) -> irast.OnConflictClause:

    with ctx.new() as constraint_ctx:
        constraint_ctx.partial_path_prefix = stmt.subject

        # We compile the name here so we can analyze it, but we don't do
        # anything else with it.
        cspec_res = setgen.ensure_set(dispatch.compile(constraint_spec,
                                                       ctx=constraint_ctx),
                                      ctx=constraint_ctx)

    if not cspec_res.rptr:
        raise errors.QueryError(
            'ON CONFLICT argument must be a property',
            context=constraint_spec.context,
        )

    if cspec_res.rptr.source.path_id != stmt.subject.path_id:
        raise errors.QueryError(
            'ON CONFLICT argument must be a property of the '
            'type being inserted',
            context=constraint_spec.context,
        )

    schema = ctx.env.schema
    schema, ptr = (typeutils.ptrcls_from_ptrref(cspec_res.rptr.ptrref,
                                                schema=schema))
    if not isinstance(ptr, s_pointers.Pointer):
        raise errors.QueryError(
            'ON CONFLICT property must be a property',
            context=constraint_spec.context,
        )

    ptr = ptr.get_nearest_non_derived_parent(schema)
    if ptr.get_cardinality(schema) != qltypes.SchemaCardinality.ONE:
        raise errors.QueryError(
            'ON CONFLICT property must be a SINGLE property',
            context=constraint_spec.context,
        )

    exclusive_constr: s_constr.Constraint = schema.get('std::exclusive')
    ex_cnstrs = [
        c for c in ptr.get_constraints(schema).objects(schema)
        if c.issubclass(schema, exclusive_constr)
    ]

    if len(ex_cnstrs) != 1:
        raise errors.QueryError(
            'ON CONFLICT property must have a single exclusive constraint',
            context=constraint_spec.context,
        )

    module_id = schema.get_global(s_mod.Module, ptr.get_name(schema).module).id

    field_name = cspec_res.rptr.ptrref.shortname

    # Find the IR corresponding to our field
    # FIXME: Is there a better way to do this?
    for elem, _ in stmt.subject.shape:
        if elem.rptr.ptrref.shortname == field_name:
            key = elem.expr
            break
    else:
        raise errors.QueryError(
            'INSERT ON CONFLICT property requires matching shape',
            context=constraint_spec.context,
        )

    # FIXME: This reuse of the source
    ctx.anchors = ctx.anchors.copy()
    source_alias = ctx.aliases.get('a')
    ctx.anchors[source_alias] = setgen.ensure_set(key, ctx=ctx)
    anchor = qlast.Path(steps=[qlast.ObjectRef(name=source_alias)])

    ctx.env.schema = schema

    # Compile an else branch
    else_info = None
    if else_branch:
        # Produce a query that finds the conflicting objects
        nobe = qlast.SelectQuery(
            result=insert_subject,
            where=qlast.BinOp(op='=', left=constraint_spec, right=anchor),
        )
        select_ir = dispatch.compile(nobe, ctx=ctx)
        select_ir = setgen.scoped_set(select_ir, force_reassign=True, ctx=ctx)
        assert isinstance(select_ir, irast.Set)

        # The ELSE needs to be able to reference the subject in an
        # UPDATE, even though that would normally be prohibited.
        ctx.path_scope.factoring_allowlist.add(stmt.subject.path_id)

        # Compile else
        else_ir = dispatch.compile(astutils.ensure_qlstmt(else_branch),
                                   ctx=ctx)
        assert isinstance(else_ir, irast.Set)
        else_info = irast.OnConflictElse(select_ir, else_ir)

    return irast.OnConflictClause(
        irast.ConstraintRef(id=ex_cnstrs[0].id, module_id=module_id),
        else_info)
コード例 #6
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_NOT_ILIKE_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op='NOT ILIKE',
                            right=kids[3].val)
コード例 #7
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_NOT_IN_Expr(self, *kids):
     inexpr = kids[3].val
     self.val = qlast.BinOp(left=kids[0].val, op='NOT IN', right=inexpr)
コード例 #8
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_OR_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op=kids[1].val.upper(),
                            right=kids[2].val)
コード例 #9
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_LIKE_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val, op='LIKE', right=kids[2].val)
コード例 #10
0
ファイル: expressions.py プロジェクト: LeonardAukea/edgedb
 def reduce_Expr_RANGBRACKET_Expr(self, *kids):
     self.val = qlast.BinOp(left=kids[0].val,
                            op=kids[1].val,
                            right=kids[2].val)
コード例 #11
0
ファイル: stmt.py プロジェクト: signupsi/edgedb
        ])
        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,
            ))

    for constr in obj_constrs:
        subjectexpr = constr.get_subjectexpr(ctx.env.schema)
        assert subjectexpr and isinstance(subjectexpr.qlast, qlast.Expr)
        lhs = qlutils.subject_paths_substitute(subjectexpr.qlast, ptr_anchors)
        rhs = qlutils.subject_substitute(subjectexpr.qlast, insert_subject)
        conds.append(qlast.BinOp(op='=', left=lhs, right=rhs))

    # We use `any` to compute the disjunction here because some might
    # be empty.
    if len(conds) == 1:
        cond = conds[0]
    else:
        cond = qlast.FunctionCall(
コード例 #12
0
ファイル: viewgen.py プロジェクト: awserv/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.SchemaName] = None,
                  is_insert: bool = False,
                  is_update: bool = False,
                  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.Name(
            module=ctx.derived_target_module or '__derived__',
            name=name,
        )

    view_scls = schemactx.derive_view(stype,
                                      is_insert=is_insert,
                                      is_update=is_update,
                                      derived_name=view_name,
                                      ctx=ctx)
    assert isinstance(view_scls, s_objtypes.ObjectType)
    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_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

            default_expr = ptrcls.get_default(ctx.env.schema)
            if not default_expr:
                if ptrcls.get_required(ctx.env.schema):
                    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')):
                            continue

                        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,
                    ), ),
                ], ),
                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.introspection_schema_rewrites):
        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

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

            with ctx.newscope(fenced=True) as scopectx:
                implicit_ql = qlast.ShapeElement(
                    expr=qlast.Path(steps=[
                        qlast.Ptr(ptr=qlast.ObjectRef(name=pn, ), ),
                    ], ),
                    compexpr=qlast.BinOp(
                        left=qlast.Path(
                            partial=True,
                            steps=[
                                qlast.Ptr(
                                    ptr=qlast.ObjectRef(name=pn),
                                    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 is not stype):
        ctx.env.schema = view_scls.set_field_value(ctx.env.schema, 'rptr',
                                                   view_rptr.ptrcls)

    return view_scls
コード例 #13
0
ファイル: setgen.py プロジェクト: isidentical/edgedb
def computable_ptr_set(
    rptr: irast.Pointer,
    *,
    unnest_fence: bool = False,
    same_computable_scope: bool = False,
    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
        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.introspection_schema_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')

            schema_qlexpr = qlparser.parse(comp_expr.text)

        # 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)
    with newctx() as subctx:
        subctx.disable_shadowing.add(ptrcls)
        subctx.view_scls = result_stype
        assert isinstance(source_scls, s_sources.Source)
        subctx.view_rptr = context.ViewRPtr(source_scls,
                                            ptrcls=ptrcls,
                                            rptr=rptr)
        subctx.anchors[qlast.Source().name] = source_set
        subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema)
        subctx.partial_path_prefix = source_set

        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_copy = new_set_from_set(comp_ir_set, ctx=ctx)
    pending_cardinality = ctx.pending_cardinality.get(ptrcls)
    if pending_cardinality is not None:
        stmtctx.get_pointer_cardinality_later(
            ptrcls=ptrcls,
            irexpr=comp_ir_set_copy,
            specified_card=pending_cardinality.specified_cardinality,
            is_mut_assignment=pending_cardinality.is_mut_assignment,
            shape_op=pending_cardinality.shape_op,
            source_ctx=pending_cardinality.source_ctx,
            ctx=ctx,
        )

    if (pending_cardinality is None
            or pending_cardinality.shape_op is not qlast.ShapeOp.SUBTRACT):
        # When doing subtraction in shapes, the cardinality of the
        # expression being subtracted does not matter.
        stmtctx.enforce_pointer_cardinality(
            ptrcls,
            comp_ir_set_copy,
            singletons={source_path_id},
            ctx=ctx,
        )

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

    rptr.target = comp_ir_set

    return comp_ir_set
コード例 #14
0
ファイル: translator.py プロジェクト: rafaelrosabal/edgedb
    def _get_general_offset_limit(self, after, before, first, last):
        # convert any static values to corresponding qlast
        if after is not None:
            if isinstance(after, qlast.Base):
                after = qlast.TypeCast(type=qlast.TypeName(
                    maintype=qlast.ObjectRef(name='int64')),
                                       expr=after)
            else:
                after = qlast.BaseConstant.from_python(after)
        if before is not None:
            if isinstance(before, qlast.Base):
                before = qlast.TypeCast(type=qlast.TypeName(
                    maintype=qlast.ObjectRef(name='int64')),
                                        expr=before)
            else:
                before = qlast.BaseConstant.from_python(before)
        if first is not None and not isinstance(first, qlast.Base):
            first = qlast.BaseConstant.from_python(first)
        if last is not None and not isinstance(last, qlast.Base):
            last = qlast.BaseConstant.from_python(last)

        offset = limit = None
        # convert before, after, first and last into offset and limit
        if after is not None:
            # The +1 is to make 'after' into an appropriate index.
            #
            # 0--a--1--b--2--c--3-- ... we call element at
            # index 0 (or "element 0" for short), the element
            # immediately after the mark 0. So after "element
            # 0" really means after "index 1".
            offset = qlast.BinOp(left=after,
                                 op='+',
                                 right=qlast.IntegerConstant(value='1'))

        if before is not None:
            # limit = before - (after or 0)
            if after:
                limit = qlast.BinOp(left=before, op='-', right=after)
            else:
                limit = before

        if first is not None:
            if limit is None:
                limit = first
            else:
                limit = qlast.IfElse(if_expr=first,
                                     condition=qlast.BinOp(left=first,
                                                           op='<',
                                                           right=limit),
                                     else_expr=limit)

        if last is not None:
            if limit is not None:
                if offset:
                    offset = qlast.BinOp(left=offset,
                                         op='+',
                                         right=qlast.BinOp(left=limit,
                                                           op='-',
                                                           right=last))
                else:
                    offset = qlast.BinOp(left=limit, op='-', right=last)

                limit = qlast.IfElse(if_expr=last,
                                     condition=qlast.BinOp(left=last,
                                                           op='<',
                                                           right=limit),
                                     else_expr=limit)

            else:
                # FIXME: there wasn't any limit, so we can define last
                # in terms of offset alone without negative OFFSET
                # implementation
                raise g_errors.GraphQLTranslationError(
                    f'last translates to a negative OFFSET in '
                    f'EdgeQL which is currently unsupported')

        return offset, limit