예제 #1
0
def decompile_ir(ir_tree,
                 inline_anchors=False,
                 return_statement=False,
                 *,
                 schema):
    decompiler = IRDecompiler()
    qltree = decompiler.transform(ir_tree,
                                  inline_anchors=inline_anchors,
                                  schema=schema)
    if return_statement and not isinstance(qltree, qlast.Statement):
        qltree = qlast.SelectQuery(result=qltree)
    return qltree
예제 #2
0
    def reduce_Select(self, *kids):
        r"%reduce SELECT OptionallyAliasedExpr \
                  OptFilterClause OptSortClause OptSelectLimit"

        self.val = qlast.SelectQuery(
            result=kids[1].val.expr,
            result_alias=kids[1].val.alias,
            where=kids[2].val,
            orderby=kids[3].val,
            offset=kids[4].val[0],
            limit=kids[4].val[1],
        )
예제 #3
0
 def _encode_default(self, schema, context, node, op):
     if op.new_value:
         expr = op.new_value
         if not isinstance(expr, s_expr.Expression):
             expr_t = qlast.SelectQuery(
                 result=qlast.BaseConstant.from_python(expr))
             op.new_value = s_expr.Expression.from_ast(
                 expr_t,
                 schema,
                 context.modaliases,
             )
         super()._apply_field_ast(schema, context, node, op)
예제 #4
0
파일: spec.py 프로젝트: syyunn/edgedb
def generate_config_query(schema) -> str:
    cfg = schema.get('cfg::Config')

    ref = qlast.ObjectRef(name='Config', module='cfg')
    query = qlast.SelectQuery(
        result=qlast.Shape(
            expr=qlast.Path(steps=[ref]),
            elements=qlcompiler.get_config_type_shape(schema, cfg, path=[ref]),
        ),
        limit=qlast.IntegerConstant(value='1', ),
    )

    return qlcodegen.generate_source(query)
예제 #5
0
    def _compile_view_expr(cls, expr, classname, schema, context):
        from edb.edgeql import compiler as qlcompiler

        ir = context.get_cached((expr, classname))
        if ir is None:
            if not isinstance(expr, qlast.Statement):
                expr = qlast.SelectQuery(result=expr)
            ir = qlcompiler.compile_ast_to_ir(
                expr, schema, derived_target_module=classname.module,
                result_view_name=classname, modaliases=context.modaliases,
                schema_view_mode=True)
            context.cache_value((expr, classname), ir)

        return ir
예제 #6
0
def compile_call_arg(
        arg_ql: qlast.Expr, *,
        ctx: context.ContextLevel) -> Tuple[irast.Set, context.ContextLevel]:
    with ctx.new() as argctx:
        argctx.path_log = []
        # We put on a SET OF fence preemptively in case this is
        # a SET OF arg, which we don't know yet due to polymorphic
        # matching.  We will remove it if necessary in `finalize_args()`.
        # Similarly, delay the decision to inject the implicit limit to
        # `finalize_args()`.
        arg_ql = qlast.SelectQuery(result=arg_ql,
                                   context=arg_ql.context,
                                   implicit=True)
        argctx.inhibit_implicit_limit = True
        return dispatch.compile(arg_ql, ctx=argctx), argctx
예제 #7
0
파일: config.py 프로젝트: stjordanis/edgedb
def compile_ConfigReset(
    expr: qlast.ConfigReset,
    *,
    ctx: context.ContextLevel,
) -> irast.Set:

    info = _validate_op(expr, ctx=ctx)
    filter_expr = expr.where
    select_ir = None

    if not info.param_type.is_object_type() and filter_expr is not None:
        raise errors.QueryError(
            'RESET of a primitive configuration parameter '
            'must not have a FILTER clause',
            context=expr.context,
        )

    elif isinstance(info.param_type, s_objtypes.ObjectType):
        param_type_name = info.param_type.get_name(ctx.env.schema)
        param_type_ref = qlast.ObjectRef(
            name=param_type_name.name,
            module=param_type_name.module,
        )
        select = qlast.SelectQuery(
            result=qlast.Shape(
                expr=qlast.Path(steps=[param_type_ref]),
                elements=s_utils.get_config_type_shape(ctx.env.schema,
                                                       info.param_type,
                                                       path=[param_type_ref]),
            ),
            where=filter_expr,
        )

        ctx.modaliases[None] = 'cfg'
        select_ir = setgen.ensure_set(dispatch.compile(select, ctx=ctx),
                                      ctx=ctx)

    config_reset = irast.ConfigReset(
        name=info.param_name,
        cardinality=info.cardinality,
        scope=expr.scope,
        requires_restart=info.requires_restart,
        backend_setting=info.backend_setting,
        context=expr.context,
        selector=select_ir,
    )
    return setgen.ensure_set(config_reset, ctx=ctx)
예제 #8
0
def eval_ShapeElement(el: qlast.ShapeElement, ctx: EvalContext) -> Result:
    if el.compexpr:
        result = el.compexpr
    else:
        result = qlast.Path(partial=True, steps=el.expr.steps)

    if el.elements:
        result = qlast.Shape(expr=result, elements=el.elements)

    fake_select = qlast.SelectQuery(
        result=result,
        orderby=el.orderby,
        where=el.where,
        limit=el.limit,
        offset=el.offset,
    )

    return eval(fake_select, ctx=ctx)
예제 #9
0
    def _compile_alias_expr(
        self,
        expr: qlast.Base,
        classname: sn.QualName,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> irast.Statement:
        cached: Optional[irast.Statement] = (
            context.get_cached((expr, classname)))
        if cached is not None:
            return cached

        if not isinstance(expr, qlast.Statement):
            expr = qlast.SelectQuery(result=expr)

        existing = schema.get(classname, type=s_types.Type, default=None)
        if existing is not None:
            drop_cmd = existing.init_delta_command(schema, sd.DeleteObject)
            with context.suspend_dep_verification():
                schema = drop_cmd.apply(schema, context)

        ir = qlcompiler.compile_ast_to_ir(
            expr,
            schema,
            options=qlcompiler.CompilerOptions(
                derived_target_module=classname.module,
                result_view_name=classname,
                modaliases=context.modaliases,
                schema_view_mode=True,
                in_ddl_context_name='alias definition',
            ),
        )

        if ir.volatility == qltypes.Volatility.Volatile:
            srcctx = self.get_attribute_source_context('expr')
            raise errors.SchemaDefinitionError(
                f'volatile functions are not permitted in schema-defined '
                f'computables',
                context=srcctx
            )

        context.cache_value((expr, classname), ir)

        return ir  # type: ignore
예제 #10
0
    def visit_SelectStmt(self, node):
        result = qlast.SelectQuery()

        if node.where is not None:
            result.where = self.visit(node.where)

        if node.orderby:
            result.orderby = self.visit(node.orderby)

        if node.offset is not None:
            result.offset = self.visit(node.offset)

        if node.limit is not None:
            result.limit = self.visit(node.limit)

        if node.result is not None:
            result.result = self.visit(node.result)

        return result
예제 #11
0
    def _compile_and_apply_delta_command(self, ctx: CompileContext,
                                         cmd) -> dbstate.BaseQuery:

        current_tx = ctx.state.current_tx()
        schema = current_tx.get_schema()
        context = self._new_delta_context(ctx)

        if current_tx.is_implicit():
            if isinstance(cmd, s_deltas.CreateDelta):
                command = 'CREATE MIGRATION'
            elif isinstance(cmd, s_deltas.GetDelta):
                command = 'GET MIGRATION'
            else:
                command = 'COMMIT MIGRATION'
            raise errors.QueryError(
                f'{command} must be executed in a transaction block')

        if isinstance(cmd, s_deltas.CreateDelta):
            delta = None
        else:
            delta = schema.get(cmd.classname)

        with context(s_deltas.DeltaCommandContext(schema, cmd, delta)):
            if isinstance(cmd, s_deltas.CommitDelta):
                ddl_plan = s_delta.DeltaRoot(canonical=True)
                ddl_plan.update(delta.get_commands(schema))
                return self._compile_and_apply_ddl_command(ctx, ddl_plan)

            elif isinstance(cmd, s_deltas.GetDelta):
                delta_ql = s_ddl.ddl_text_from_delta(schema, delta)
                query_ql = qlast.SelectQuery(result=qlast.StringConstant(
                    quote="'", value=ql_quote.escape_string(delta_ql)))
                return self._compile_ql_query(ctx, query_ql)

            elif isinstance(cmd, s_deltas.CreateDelta):
                schema, _ = cmd.apply(schema, context)
                current_tx.update_schema(schema)
                # We must return *some* SQL; return a no-op command.
                return dbstate.DDLQuery(sql=(b'SELECT NULL LIMIT 0;', ))

            else:
                raise errors.InternalServerError(
                    f'unexpected delta command: {cmd!r}')  # pragma: no cover
예제 #12
0
def imprint_expr_context(
    qltree: qlast_.Base,
    modaliases: Mapping[Optional[str], str],
) -> qlast_.Base:
    # Imprint current module aliases as explicit
    # alias declarations in the expression.

    if (isinstance(qltree, qlast_.BaseConstant)
            or qltree is None
            or (isinstance(qltree, qlast_.Set)
                and not qltree.elements)
            or (isinstance(qltree, qlast_.Array)
                and all(isinstance(el, qlast_.BaseConstant)
                        for el in qltree.elements))):
        # Leave constants alone.
        return qltree

    if not isinstance(qltree, qlast_.Command):
        assert isinstance(qltree, qlast_.Expr)
        qltree = qlast_.SelectQuery(result=qltree, implicit=True)
    else:
        qltree = copy.copy(qltree)
        qltree.aliases = (
            list(qltree.aliases) if qltree.aliases is not None else None)

    existing_aliases: Dict[Optional[str], str] = {}
    for alias in (qltree.aliases or ()):
        if isinstance(alias, qlast_.ModuleAliasDecl):
            existing_aliases[alias.alias] = alias.module

    aliases_to_add = set(modaliases) - set(existing_aliases)
    for alias_name in aliases_to_add:
        if qltree.aliases is None:
            qltree.aliases = []
        qltree.aliases.append(
            qlast_.ModuleAliasDecl(
                alias=alias_name,
                module=modaliases[alias_name],
            )
        )

    return qltree
예제 #13
0
    def _compile_alias_expr(
        self,
        expr: qlast.Base,
        classname: sn.SchemaName,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> irast.Statement:
        cached: Optional[irast.Statement] = (context.get_cached(
            (expr, classname)))
        if cached is not None:
            return cached

        if not isinstance(expr, qlast.Statement):
            expr = qlast.SelectQuery(result=expr)

        existing = schema.get(classname, type=s_types.Type, default=None)
        if existing is not None:
            drop_cmd_cls = sd.ObjectCommandMeta.get_command_class_or_die(
                sd.DeleteObject,
                type(existing),
            )
            drop_cmd = drop_cmd_cls(classname=classname)
            with context.suspend_dep_verification():
                schema = drop_cmd.apply(schema, context)

        ir = qlcompiler.compile_ast_to_ir(
            expr,
            schema,
            options=qlcompiler.CompilerOptions(
                derived_target_module=classname.module,
                result_view_name=classname,
                modaliases=context.modaliases,
                schema_view_mode=True,
            ),
        )

        context.cache_value((expr, classname), ir)

        return ir  # type: ignore
예제 #14
0
    def _visit_query(self, node):
        # populate input variables with defaults, where applicable
        if node.variable_definitions:
            self.visit(node.variable_definitions)

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

        # 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='stdgraphql')]),
                                                     elements=[]),
                                  limit=qlast.IntegerConstant(value='1'))

        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
예제 #15
0
파일: setgen.py 프로젝트: signupsi/edgedb
def new_set(
    *,
    stype: s_types.Type,
    ctx: context.ContextLevel,
    ircls: Type[irast.Set] = irast.Set,
    **kwargs: Any,
) -> irast.Set:
    """Create a new ir.Set instance with given attributes.

    Absolutely all ir.Set instances must be created using this
    constructor.
    """
    if (
        stype not in ctx.type_rewrites
        and isinstance(stype, s_objtypes.ObjectType)
        and ctx.env.options.apply_query_rewrites
        and (filters := stype.get_access_policy_filters(ctx.env.schema))
    ):
        qry = qlast.SelectQuery(
            result=qlast.Path(
                steps=[s_utils.name_to_ast_ref(stype.get_name(ctx.env.schema))]
            ),
        )
        for f in filters:
            assert isinstance(f.qlast, qlast.Expr)
            qry.where = astutils.extend_binop(qry.where, f.qlast)

        with ctx.detached() as subctx:
            subctx.expr_exposed = False
            # This is a global rewrite operation that is done once
            # per type, and so we don't really care if we're in a
            # temporary scope or not.
            subctx.path_scope = subctx.env.path_scope.root
            subctx.in_temp_scope = False
            # Put a placeholder to prevent recursion.
            subctx.type_rewrites[stype] = irast.Set()
            filtered_set = dispatch.compile(qry, ctx=subctx)
            assert isinstance(filtered_set, irast.Set)
            subctx.type_rewrites[stype] = filtered_set
예제 #16
0
파일: func.py 프로젝트: stjordanis/edgedb
def compile_arg(arg_ql: qlast.Expr,
                typemod: ft.TypeModifier,
                *,
                in_conditional: bool = False,
                ctx: context.ContextLevel) -> irast.Set:
    fenced = typemod is ft.TypeModifier.SetOfType
    optional = typemod is ft.TypeModifier.OptionalType

    # Create a a branch for OPTIONAL ones. The OPTIONAL branch is to
    # have a place to mark as optional in the scope tree.
    # For fenced arguments we instead wrap it in a SELECT below.
    new = ctx.newscope(fenced=False) if optional else ctx.new()
    with new as argctx:
        if in_conditional:
            argctx.in_conditional = arg_ql.context

        if optional:
            argctx.path_scope.mark_as_optional()

        if fenced:
            arg_ql = qlast.SelectQuery(result=arg_ql,
                                       context=arg_ql.context,
                                       implicit=True,
                                       rptr_passthrough=True)

        argctx.inhibit_implicit_limit = True

        arg_ir = dispatch.compile(arg_ql, ctx=argctx)

        if optional:
            pathctx.register_set_in_scope(arg_ir, optional=True, ctx=ctx)

            if arg_ir.path_scope_id is None:
                pathctx.assign_set_scope(arg_ir, argctx.path_scope, ctx=argctx)

        return arg_ir
예제 #17
0
def compile_DeleteQuery(
        expr: qlast.DeleteQuery, *, ctx: context.ContextLevel) -> irast.Set:

    if ctx.in_conditional is not None:
        raise errors.QueryError(
            'DELETE statements cannot be used inside conditional expressions',
            context=expr.context,
        )

    with ctx.subquery() as ictx:
        stmt = irast.DeleteStmt()
        # Expand the DELETE from sugar into full DELETE (SELECT ...)
        # form, if there's any additional clauses.
        if any([expr.where, expr.orderby, expr.offset, expr.limit]):
            if expr.offset or expr.limit:
                subjql = qlast.SelectQuery(
                    result=qlast.SelectQuery(
                        result=expr.subject,
                        result_alias=expr.subject_alias,
                        where=expr.where,
                        orderby=expr.orderby,
                        context=expr.context,
                        implicit=True,
                    ),
                    limit=expr.limit,
                    offset=expr.offset,
                    context=expr.context,
                )
            else:
                subjql = qlast.SelectQuery(
                    result=expr.subject,
                    result_alias=expr.subject_alias,
                    where=expr.where,
                    orderby=expr.orderby,
                    offset=expr.offset,
                    limit=expr.limit,
                    context=expr.context,
                )

            expr = qlast.DeleteQuery(
                aliases=expr.aliases,
                context=expr.context,
                subject=subjql,
            )

        init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx)

        # DELETE Expr is a delete(SET OF X), so we need a scope fence.
        with ictx.newscope(fenced=True) as scopectx:
            scopectx.implicit_limit = 0
            subject = setgen.scoped_set(
                dispatch.compile(expr.subject, ctx=scopectx), ctx=scopectx)

        subj_type = inference.infer_type(subject, ictx.env)
        if not isinstance(subj_type, s_objtypes.ObjectType):
            raise errors.QueryError(
                f'cannot delete non-ObjectType objects',
                context=expr.subject.context
            )

        with ictx.new() as bodyctx:
            bodyctx.implicit_id_in_shapes = False
            bodyctx.implicit_tid_in_shapes = False
            stmt.subject = compile_query_subject(
                subject, shape=None, ctx=bodyctx)

        stmt_subject_stype = setgen.get_set_type(subject, ctx=ictx)
        result = setgen.class_set(
            schemactx.get_material_type(stmt_subject_stype, ctx=ctx),
            path_id=stmt.subject.path_id,
            ctx=ctx,
        )

        with ictx.new() as resultctx:
            if ictx.stmt is ctx.toplevel_stmt:
                resultctx.expr_exposed = True

            stmt.result = compile_query_subject(
                result,
                view_scls=ctx.view_scls,
                view_name=ctx.toplevel_result_view_name,
                compile_views=ictx.stmt is ictx.toplevel_stmt,
                ctx=resultctx,
            )

        result = fini_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx)

    return result
예제 #18
0
파일: viewgen.py 프로젝트: sbdchd/edgedb
def _normalize_view_ptr_expr(
        shape_el: qlast.ShapeElement,
        view_scls: s_objtypes.ObjectType, *,
        path_id: irast.PathId,
        path_id_namespace: Optional[irast.WeakNamespace]=None,
        is_insert: bool=False,
        is_update: bool=False,
        from_default: bool=False,
        view_rptr: Optional[context.ViewRPtr]=None,
        ctx: context.ContextLevel) -> s_pointers.Pointer:
    steps = shape_el.expr.steps
    is_linkprop = False
    is_polymorphic = False
    is_mutation = is_insert or is_update
    # Pointers may be qualified by the explicit source
    # class, which is equivalent to Expr[IS Type].
    plen = len(steps)
    ptrsource: s_sources.Source = view_scls
    qlexpr: Optional[qlast.Expr] = None
    target_typexpr = None
    source: qlast.Base
    base_ptrcls_is_alias = False

    if plen >= 2 and isinstance(steps[-1], qlast.TypeIntersection):
        # Target type intersection: foo: Type
        target_typexpr = steps[-1].type
        plen -= 1
        steps = steps[:-1]

    if plen == 1:
        # regular shape
        lexpr = steps[0]
        assert isinstance(lexpr, qlast.Ptr)
        is_linkprop = lexpr.type == 'property'
        if is_linkprop:
            if view_rptr is None or view_rptr.ptrcls is None:
                raise errors.QueryError(
                    'invalid reference to link property '
                    'in top level shape', context=lexpr.context)
            assert isinstance(view_rptr.ptrcls, s_links.Link)
            ptrsource = view_rptr.ptrcls
        source = qlast.Source()
    elif plen == 2 and isinstance(steps[0], qlast.TypeIntersection):
        # Source type intersection: [IS Type].foo
        source = qlast.Path(steps=[
            qlast.Source(),
            steps[0],
        ])
        lexpr = steps[1]
        ptype = steps[0].type
        if not isinstance(ptype, qlast.TypeName):
            raise errors.QueryError(
                'complex type expressions are not supported here',
                context=ptype.context,
            )
        source_spec = schemactx.get_schema_type(ptype.maintype, ctx=ctx)
        if not isinstance(source_spec, s_objtypes.ObjectType):
            raise errors.QueryError(
                f'expected object type, got '
                f'{source_spec.get_verbosename(ctx.env.schema)}',
                context=ptype.context,
            )
        ptrsource = source_spec
        is_polymorphic = True
    else:  # pragma: no cover
        raise RuntimeError(
            f'unexpected path length in view shape: {len(steps)}')

    assert isinstance(lexpr, qlast.Ptr)
    ptrname = lexpr.ptr.name

    compexpr: Optional[qlast.Expr] = shape_el.compexpr
    if compexpr is None and is_insert and shape_el.elements:
        # Short shape form in INSERT, e.g
        #     INSERT Foo { bar: Spam { name := 'name' }}
        # is prohibited.
        raise errors.EdgeQLSyntaxError(
            "unexpected ':'", context=steps[-1].context)

    ptrcls: Optional[s_pointers.Pointer]

    if compexpr is None:
        ptrcls = setgen.resolve_ptr(
            ptrsource, ptrname, track_ref=lexpr, ctx=ctx)
        if is_polymorphic:
            ptrcls = schemactx.derive_ptr(
                ptrcls, view_scls,
                is_insert=is_insert,
                is_update=is_update,
                ctx=ctx)

        base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema)
        base_ptr_is_computable = base_ptrcls in ctx.source_map
        ptr_name = sn.QualName(
            module='__',
            name=ptrcls.get_shortname(ctx.env.schema).name,
        )

        base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx)
        base_is_singleton = False
        if base_cardinality is not None and base_cardinality.is_known():
            base_is_singleton = base_cardinality.is_single()

        if (
            shape_el.where
            or shape_el.orderby
            or shape_el.offset
            or shape_el.limit
            or base_ptr_is_computable
            or is_polymorphic
            or target_typexpr is not None
            or (ctx.implicit_limit and not base_is_singleton)
        ):

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

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            assert isinstance(qlexpr, qlast.SelectQuery)
            qlexpr.where = shape_el.where
            qlexpr.orderby = shape_el.orderby

            if shape_el.offset or shape_el.limit:
                qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True)
                qlexpr.offset = shape_el.offset
                qlexpr.limit = shape_el.limit

            if (
                (ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt)
                and not qlexpr.limit
                and ctx.implicit_limit
                and not base_is_singleton
            ):
                qlexpr.limit = qlast.IntegerConstant(
                    value=str(ctx.implicit_limit),
                )

        if target_typexpr is not None:
            assert isinstance(target_typexpr, qlast.TypeName)
            intersector_type = schemactx.get_schema_type(
                target_typexpr.maintype, ctx=ctx)

            int_result = schemactx.apply_intersection(
                ptrcls.get_target(ctx.env.schema),  # type: ignore
                intersector_type,
                ctx=ctx,
            )

            ptr_target = int_result.stype
        else:
            _ptr_target = ptrcls.get_target(ctx.env.schema)
            assert _ptr_target
            ptr_target = _ptr_target

        ptr_cardinality = base_cardinality
        if ptr_cardinality is None or not ptr_cardinality.is_known():
            # We do not know the parent's pointer cardinality yet.
            ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls)
            ctx.env.pointer_specified_info[ptrcls] = (
                shape_el.cardinality, shape_el.required, shape_el.context)

        implicit_tid = has_implicit_type_computables(
            ptr_target,
            is_mutation=is_mutation,
            ctx=ctx,
        )

        if shape_el.elements or implicit_tid:
            sub_view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                is_insert=is_insert,
                is_update=is_update)

            sub_path_id = pathctx.extend_path_id(
                path_id,
                ptrcls=base_ptrcls,
                ns=ctx.path_id_namespace,
                ctx=ctx)

            ctx.path_scope.attach_path(sub_path_id,
                                       context=shape_el.context)

            if not isinstance(ptr_target, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'shapes cannot be applied to '
                    f'{ptr_target.get_verbosename(ctx.env.schema)}',
                    context=shape_el.context,
                )

            if is_update:
                for subel in shape_el.elements or []:
                    is_prop = (
                        isinstance(subel.expr.steps[0], qlast.Ptr) and
                        subel.expr.steps[0].type == 'property'
                    )
                    if not is_prop:
                        raise errors.QueryError(
                            'only references to link properties are allowed '
                            'in nested UPDATE shapes', context=subel.context)

                ptr_target = _process_view(
                    stype=ptr_target, path_id=sub_path_id,
                    path_id_namespace=path_id_namespace,
                    view_rptr=sub_view_rptr,
                    elements=shape_el.elements, is_update=True,
                    parser_context=shape_el.context,
                    ctx=ctx)
            else:
                ptr_target = _process_view(
                    stype=ptr_target, path_id=sub_path_id,
                    path_id_namespace=path_id_namespace,
                    view_rptr=sub_view_rptr,
                    elements=shape_el.elements,
                    parser_context=shape_el.context,
                    ctx=ctx)

    else:
        base_ptrcls = ptrcls = None

        if (is_mutation
                and ptrname not in ctx.special_computables_in_mutation_shape):
            # If this is a mutation, the pointer must exist.
            ptrcls = setgen.resolve_ptr(
                ptrsource, ptrname, track_ref=lexpr, ctx=ctx)

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

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

        else:
            ptr_name = sn.QualName(
                module='__',
                name=ptrname,
            )

            try:
                ptrcls = setgen.resolve_ptr(
                    ptrsource,
                    ptrname,
                    track_ref=False,
                    ctx=ctx,
                )

                base_ptrcls = ptrcls.get_bases(
                    ctx.env.schema).first(ctx.env.schema)
            except errors.InvalidReferenceError:
                # This is a NEW computable pointer, it's fine.
                pass

        qlexpr = astutils.ensure_qlstmt(compexpr)

        if ((ctx.expr_exposed or ctx.stmt is ctx.toplevel_stmt)
                and ctx.implicit_limit
                and isinstance(qlexpr, qlast.OffsetLimitMixin)
                and not qlexpr.limit):
            qlexpr.limit = qlast.IntegerConstant(value=str(ctx.implicit_limit))

        with ctx.newscope(fenced=True) as shape_expr_ctx:
            # Put current pointer class in context, so
            # that references to link properties in sub-SELECT
            # can be resolved.  This is necessary for proper
            # evaluation of link properties on computable links,
            # most importantly, in INSERT/UPDATE context.
            shape_expr_ctx.view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                ptrcls_name=ptr_name,
                ptrcls_is_linkprop=is_linkprop,
                is_insert=is_insert,
                is_update=is_update,
            )

            shape_expr_ctx.defining_view = view_scls
            shape_expr_ctx.path_scope.unnest_fence = True
            shape_expr_ctx.partial_path_prefix = setgen.class_set(
                view_scls.get_bases(ctx.env.schema).first(ctx.env.schema),
                path_id=path_id, ctx=shape_expr_ctx)
            prefix_rptrref = path_id.rptr()
            if prefix_rptrref is not None:
                # Source path seems to contain multiple steps,
                # so set up a rptr for abbreviated link property
                # paths.
                src_path_id = path_id.src_path()
                assert src_path_id is not None
                ctx.env.schema, src_t = irtyputils.ir_typeref_to_type(
                    shape_expr_ctx.env.schema,
                    src_path_id.target,
                )
                prefix_rptr = irast.Pointer(
                    source=setgen.class_set(
                        src_t,
                        path_id=src_path_id,
                        ctx=shape_expr_ctx,
                    ),
                    target=shape_expr_ctx.partial_path_prefix,
                    ptrref=prefix_rptrref,
                    direction=s_pointers.PointerDirection.Outbound,
                )
                shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr

            if is_mutation and ptrcls is not None:
                shape_expr_ctx.expr_exposed = True
                shape_expr_ctx.empty_result_type_hint = \
                    ptrcls.get_target(ctx.env.schema)

            shape_expr_ctx.stmt_metadata[qlexpr] = context.StatementMetadata(
                iterator_target=True,
            )
            irexpr = dispatch.compile(qlexpr, ctx=shape_expr_ctx)

            if (
                shape_el.operation.op is qlast.ShapeOp.APPEND
                or shape_el.operation.op is qlast.ShapeOp.SUBTRACT
            ):
                if not is_update:
                    op = (
                        '+=' if shape_el.operation.op is qlast.ShapeOp.APPEND
                        else '-='
                    )
                    raise errors.EdgeQLSyntaxError(
                        f"unexpected '{op}'",
                        context=shape_el.operation.context,
                    )

            irexpr.context = compexpr.context

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

            if ptrcls is not None:
                ctx.env.schema = ptrcls.set_field_value(
                    ctx.env.schema, 'owned', True)

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

        if (
            isinstance(ptr_target, s_types.Collection)
            and not ctx.env.orig_schema.get_by_id(ptr_target.id, default=None)
        ):
            # Record references to implicitly defined collection types,
            # so that the alias delta machinery can pick them up.
            ctx.env.created_schema_objects.add(ptr_target)

        anytype = ptr_target.find_any(ctx.env.schema)
        if anytype is not None:
            raise errors.QueryError(
                'expression returns value of indeterminate type',
                context=ctx.env.type_origins.get(anytype),
            )

        # Validate that the insert/update expression is
        # of the correct class.
        if is_mutation and ptrcls is not None:
            base_target = ptrcls.get_target(ctx.env.schema)
            assert base_target is not None
            if ptr_target.assignment_castable_to(
                    base_target,
                    schema=ctx.env.schema):
                # Force assignment casts if the target type is not a
                # subclass of the base type and the cast is not to an
                # object type.
                if not (
                    base_target.is_object_type()
                    or s_types.is_type_compatible(
                        base_target, ptr_target, schema=ctx.env.schema
                    )
                ):
                    qlexpr = astutils.ensure_qlstmt(qlast.TypeCast(
                        type=typegen.type_to_ql_typeref(base_target, ctx=ctx),
                        expr=compexpr,
                    ))
                    ptr_target = base_target

            else:
                expected = [
                    repr(str(base_target.get_displayname(ctx.env.schema)))
                ]

                ercls: Type[errors.EdgeDBError]
                if ptrcls.is_property(ctx.env.schema):
                    ercls = errors.InvalidPropertyTargetError
                else:
                    ercls = errors.InvalidLinkTargetError

                ptr_vn = ptrcls.get_verbosename(ctx.env.schema,
                                                with_parent=True)

                raise ercls(
                    f'invalid target for {ptr_vn}: '
                    f'{str(ptr_target.get_displayname(ctx.env.schema))!r} '
                    f'(expecting {" or ".join(expected)})'
                )

    if qlexpr is not None or ptrcls is None:
        src_scls: s_sources.Source

        if is_linkprop:
            # Proper checking was done when is_linkprop is defined.
            assert view_rptr is not None
            assert isinstance(view_rptr.ptrcls, s_links.Link)
            src_scls = view_rptr.ptrcls
        else:
            src_scls = view_scls

        if ptr_target.is_object_type():
            base = ctx.env.get_track_schema_object(
                sn.QualName('std', 'link'), expr=None)
        else:
            base = ctx.env.get_track_schema_object(
                sn.QualName('std', 'property'), expr=None)

        if base_ptrcls is not None:
            derive_from = base_ptrcls
        else:
            derive_from = base

        derived_name = schemactx.derive_view_name(
            base_ptrcls,
            derived_name_base=ptr_name,
            derived_name_quals=[str(src_scls.get_name(ctx.env.schema))],
            ctx=ctx,
        )

        existing = ctx.env.schema.get(
            derived_name, default=None, type=s_pointers.Pointer)
        if existing is not None:
            existing_target = existing.get_target(ctx.env.schema)
            assert existing_target is not None
            if ctx.recompiling_schema_alias:
                ptr_cardinality = existing.get_cardinality(ctx.env.schema)
            if ptr_target == existing_target:
                ptrcls = existing
            elif ptr_target.implicitly_castable_to(
                    existing_target, ctx.env.schema):
                ctx.env.schema = existing.set_target(
                    ctx.env.schema, ptr_target)
                ptrcls = existing
            else:
                vnp = existing.get_verbosename(
                    ctx.env.schema, with_parent=True)

                t1_vn = existing_target.get_verbosename(ctx.env.schema)
                t2_vn = ptr_target.get_verbosename(ctx.env.schema)

                if compexpr is not None:
                    source_context = compexpr.context
                else:
                    source_context = shape_el.expr.steps[-1].context
                raise errors.SchemaError(
                    f'cannot redefine {vnp} as {t2_vn}',
                    details=f'{vnp} is defined as {t1_vn}',
                    context=source_context,
                )
        else:
            ptrcls = schemactx.derive_ptr(
                derive_from, src_scls, ptr_target,
                is_insert=is_insert,
                is_update=is_update,
                derived_name=derived_name,
                ctx=ctx)

    elif ptrcls.get_target(ctx.env.schema) != ptr_target:
        ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target)

    assert ptrcls is not None

    if qlexpr is None:
        # This is not a computable, just a pointer
        # to a nested shape.  Have it reuse the original
        # pointer name so that in `Foo.ptr.name` and
        # `Foo { ptr: {name}}` are the same path.
        path_id_name = base_ptrcls.get_name(ctx.env.schema)
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'path_id_name', path_id_name
        )

    if qlexpr is not None:
        ctx.source_map[ptrcls] = irast.ComputableInfo(
            qlexpr=qlexpr,
            context=ctx,
            path_id=path_id,
            path_id_ns=path_id_namespace,
            shape_op=shape_el.operation.op,
        )

    if compexpr is not None or is_polymorphic:
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema,
            'computable',
            True,
        )

        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema,
            'owned',
            True,
        )

    if ptr_cardinality is not None:
        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'cardinality', ptr_cardinality)
    else:
        if qlexpr is None and ptrcls is not base_ptrcls:
            ctx.env.pointer_derivation_map[base_ptrcls].append(ptrcls)

        base_cardinality = None
        base_required = False
        if base_ptrcls is not None and not base_ptrcls_is_alias:
            base_cardinality = _get_base_ptr_cardinality(base_ptrcls, ctx=ctx)
            base_required = base_ptrcls.get_required(ctx.env.schema)

        if base_cardinality is None or not base_cardinality.is_known():
            specified_cardinality = shape_el.cardinality
            specified_required = shape_el.required
        else:
            specified_cardinality = base_cardinality
            specified_required = base_required

            if (shape_el.cardinality is not None
                    and base_ptrcls is not None
                    and shape_el.cardinality != base_cardinality):
                base_src = base_ptrcls.get_source(ctx.env.schema)
                assert base_src is not None
                base_src_name = base_src.get_verbosename(ctx.env.schema)
                raise errors.SchemaError(
                    f'cannot redefine the cardinality of '
                    f'{ptrcls.get_verbosename(ctx.env.schema)}: '
                    f'it is defined as {base_cardinality.as_ptr_qual()!r} '
                    f'in the base {base_src_name}',
                    context=compexpr and compexpr.context,
                )
            # The required flag may be inherited from the base
            specified_required = shape_el.required or base_required

        ctx.env.pointer_specified_info[ptrcls] = (
            specified_cardinality, specified_required, shape_el.context)

        ctx.env.schema = ptrcls.set_field_value(
            ctx.env.schema, 'cardinality', qltypes.SchemaCardinality.Unknown)

    if (
        ptrcls.is_protected_pointer(ctx.env.schema)
        and qlexpr is not None
        and not from_default
        and not ctx.env.options.allow_writing_protected_pointers
    ):
        ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
        if is_polymorphic:
            msg = (f'cannot access {ptrcls_sn.name} on a polymorphic '
                   f'shape element')
        else:
            msg = f'cannot assign to {ptrcls_sn.name}'
        raise errors.QueryError(msg, context=shape_el.context)

    if is_update and ptrcls.get_readonly(ctx.env.schema):
        raise errors.QueryError(
            f'cannot update {ptrcls.get_verbosename(ctx.env.schema)}: '
            f'it is declared as read-only',
            context=compexpr and compexpr.context,
        )

    return ptrcls
예제 #19
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
예제 #20
0
파일: stmt.py 프로젝트: signupsi/edgedb
        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(
            func='any',
            args=[qlast.Set(elements=conds)],
        )

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

    select_ir = dispatch.compile(select_ast, ctx=ctx)
    select_ir = setgen.scoped_set(
        select_ir, force_reassign=True, ctx=ctx)
    assert isinstance(select_ir, irast.Set)

    return select_ir


def compile_insert_unless_conflict(
    stmt: irast.InsertStmt,
    insert_subject: qlast.Path,
    *, ctx: context.ContextLevel,
) -> irast.OnConflictClause:
예제 #21
0
파일: nodes.py 프로젝트: szhorizon/edgedb
    def _handle_view_op(cls, schema, cmd, astnode, context):
        view_expr = cls._maybe_get_view_expr(astnode)
        if view_expr is not None:
            if not isinstance(view_expr, qlast.Statement):
                view_expr = qlast.SelectQuery(result=view_expr)

            existing_aliases = {}
            for alias in view_expr.aliases:
                if isinstance(alias, qlast.ModuleAliasDecl):
                    existing_aliases[alias.alias] = alias.module

            aliases_to_add = set(context.modaliases) - set(existing_aliases)
            for alias in aliases_to_add:
                view_expr.aliases.append(
                    qlast.ModuleAliasDecl(
                        alias=alias,
                        module=context.modaliases[alias],
                    ))

            expr_ql = qlcodegen.generate_source(view_expr, pretty=False)
            cmd.set_attribute_value('expr', s_expr.Expression(text=expr_ql))

            ir = cls._compile_view_expr(view_expr, cmd.classname, schema,
                                        context)

            view_types = ir.views.values()

            if isinstance(astnode, qlast.AlterObjectType):
                prev = schema.get(cmd.classname)
                prev_ir = cls._compile_view_expr(prev.expr, cmd.classname,
                                                 schema, context)
                prev_view_types = prev_ir.views.values()
            else:
                prev_ir = None
                prev_view_types = []

            derived_delta = sd.DeltaRoot()

            new_schema = ir.schema
            old_schema = prev_ir.schema if prev_ir is not None else None

            adds_mods, dels = so.Object._delta_sets(prev_view_types,
                                                    view_types,
                                                    old_schema=old_schema,
                                                    new_schema=new_schema)

            derived_delta.update(adds_mods)
            derived_delta.update(dels)

            if ir.stype.is_view(ir.schema):
                for op in list(derived_delta.get_subcommands()):
                    if op.classname == cmd.classname:
                        for subop in op.get_subcommands():
                            if isinstance(subop, sd.AlterObjectProperty):
                                cmd.discard_attribute(subop.property)
                            cmd.add(subop)

                        derived_delta.discard(op)

            cmd.update(derived_delta.get_subcommands())
            cmd.discard_attribute('view_type')
            cmd.add(
                sd.AlterObjectProperty(property='view_type',
                                       new_value=s_types.ViewType.Select))

        return cmd
예제 #22
0
    # 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


def _constr_matters(
    constr: s_constr.Constraint, ctx: context.ContextLevel,
) -> bool:
    schema = ctx.env.schema
    return (
        not constr.generic(schema)
        and not constr.get_delegated(schema)
        and (
            constr.get_owned(schema)
            or all(anc.get_delegated(schema) or anc.generic(schema) for anc
예제 #23
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)
예제 #24
0
def _normalize_view_ptr_expr(shape_el: qlast.ShapeElement,
                             view_scls: s_types.Type,
                             *,
                             path_id: irast.PathId,
                             path_id_namespace: Optional[
                                 irast.WeakNamespace] = None,
                             is_insert: bool = False,
                             is_update: bool = False,
                             view_rptr: Optional[context.ViewRPtr] = None,
                             ctx: context.ContextLevel) -> s_pointers.Pointer:
    steps = shape_el.expr.steps
    is_linkprop = False
    is_polymorphic = False
    is_mutation = is_insert or is_update
    # Pointers may be qualified by the explicit source
    # class, which is equivalent to Expr[IS Type].
    plen = len(steps)
    ptrsource = view_scls
    qlexpr = None
    target_typexpr = None
    source: qlast.Base

    if plen >= 2 and isinstance(steps[-1], qlast.TypeIndirection):
        # Target type indirection: foo: Type
        target_typexpr = steps[-1].type
        plen -= 1
        steps = steps[:-1]

    if plen == 1:
        # regular shape
        lexpr = steps[0]
        assert isinstance(lexpr, qlast.Ptr)
        is_linkprop = lexpr.type == 'property'
        if is_linkprop:
            if view_rptr is None:
                raise errors.QueryError(
                    'invalid reference to link property '
                    'in top level shape',
                    context=lexpr.context)
            ptrsource = view_rptr.ptrcls
        source = qlast.Source()
    elif plen == 2 and isinstance(steps[0], qlast.TypeIndirection):
        # Source type indirection: [IS Type].foo
        source = qlast.Path(steps=[
            qlast.Source(),
            steps[0],
        ])
        lexpr = steps[1]
        ptype = steps[0].type
        if not isinstance(ptype, qlast.TypeName):
            raise errors.QueryError(
                'complex type expressions are not supported here',
                context=ptype.context,
            )
        ptrsource = schemactx.get_schema_type(ptype.maintype, ctx=ctx)
        is_polymorphic = True
    else:  # pragma: no cover
        raise RuntimeError(
            f'unexpected path length in view shape: {len(steps)}')

    assert isinstance(lexpr, qlast.Ptr)
    ptrname = lexpr.ptr.name

    compexpr = shape_el.compexpr
    if compexpr is None and is_insert and shape_el.elements:
        # Short shape form in INSERT, e.g
        #     INSERT Foo { bar: Spam { name := 'name' }}
        # is prohibited.
        raise errors.EdgeQLSyntaxError("unexpected ':'",
                                       context=steps[-1].context)

    if compexpr is None:
        ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx)
        if is_polymorphic:
            ptrcls = schemactx.derive_ptr(ptrcls,
                                          view_scls,
                                          is_insert=is_insert,
                                          is_update=is_update,
                                          ctx=ctx)

        base_ptrcls = ptrcls.get_bases(ctx.env.schema).first(ctx.env.schema)
        base_ptr_is_computable = base_ptrcls in ctx.source_map
        ptr_name = sn.Name(
            module='__',
            name=ptrcls.get_shortname(ctx.env.schema).name,
        )

        if (shape_el.where or shape_el.orderby or shape_el.offset
                or shape_el.limit or base_ptr_is_computable or is_polymorphic
                or target_typexpr is not None):

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

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            qlexpr.where = shape_el.where
            qlexpr.orderby = shape_el.orderby

            if shape_el.offset or shape_el.limit:
                qlexpr = qlast.SelectQuery(result=qlexpr, implicit=True)
                qlexpr.offset = shape_el.offset
                qlexpr.limit = shape_el.limit

        if target_typexpr is not None:
            ptr_target = schemactx.get_schema_type(target_typexpr.maintype,
                                                   ctx=ctx)
        else:
            ptr_target = ptrcls.get_target(ctx.env.schema)

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

        implicit_tid = has_implicit_tid(
            ptr_target,
            is_mutation=is_mutation,
            ctx=ctx,
        )

        if shape_el.elements or implicit_tid:
            sub_view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                is_insert=is_insert,
                is_update=is_update)

            sub_path_id = pathctx.extend_path_id(path_id,
                                                 ptrcls=base_ptrcls,
                                                 target=ptrcls.get_target(
                                                     ctx.env.schema),
                                                 ns=ctx.path_id_namespace,
                                                 ctx=ctx)

            ctx.path_scope.attach_path(sub_path_id)

            if is_update:
                for subel in shape_el.elements or []:
                    is_prop = (isinstance(subel.expr.steps[0], qlast.Ptr)
                               and subel.expr.steps[0].type == 'property')
                    if not is_prop:
                        raise errors.QueryError(
                            'only references to link properties are allowed '
                            'in nested UPDATE shapes',
                            context=subel.context)

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

    else:
        base_ptrcls = ptrcls = None

        if (is_mutation
                and ptrname not in ctx.special_computables_in_mutation_shape):
            # If this is a mutation, the pointer must exist.
            ptrcls = setgen.resolve_ptr(ptrsource, ptrname, ctx=ctx)

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

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

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

        qlexpr = astutils.ensure_qlstmt(compexpr)

        with ctx.newscope(fenced=True) as shape_expr_ctx:
            # Put current pointer class in context, so
            # that references to link properties in sub-SELECT
            # can be resolved.  This is necessary for proper
            # evaluation of link properties on computable links,
            # most importantly, in INSERT/UPDATE context.
            shape_expr_ctx.view_rptr = context.ViewRPtr(
                ptrsource if is_linkprop else view_scls,
                ptrcls=ptrcls,
                ptrcls_name=ptr_name,
                ptrcls_is_linkprop=is_linkprop,
                is_insert=is_insert,
                is_update=is_update)

            shape_expr_ctx.defining_view = True
            shape_expr_ctx.path_scope.unnest_fence = True
            shape_expr_ctx.partial_path_prefix = setgen.class_set(
                view_scls, path_id=path_id, ctx=shape_expr_ctx)
            prefix_rptrref = path_id.rptr()
            if prefix_rptrref is not None:
                # Source path seems to contain multiple steps,
                # so set up a rptr for abbreviated link property
                # paths.
                src_path_id = path_id.src_path()
                prefix_rptr = irast.Pointer(
                    source=setgen.class_set(
                        irtyputils.ir_typeref_to_type(
                            shape_expr_ctx.env.schema,
                            src_path_id.target,
                        ),
                        path_id=src_path_id,
                        ctx=shape_expr_ctx,
                    ),
                    target=shape_expr_ctx.partial_path_prefix,
                    ptrref=prefix_rptrref,
                    direction=s_pointers.PointerDirection.Outbound,
                )
                shape_expr_ctx.partial_path_prefix.rptr = prefix_rptr

            if is_mutation and ptrcls is not None:
                shape_expr_ctx.expr_exposed = True
                shape_expr_ctx.empty_result_type_hint = \
                    ptrcls.get_target(ctx.env.schema)

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

            irexpr.context = compexpr.context

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

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

        anytype = ptr_target.find_any(ctx.env.schema)
        if anytype is not None:
            raise errors.QueryError(
                'expression returns value of indeterminate type',
                context=ctx.env.type_origins.get(anytype),
            )

        # Validate that the insert/update expression is
        # of the correct class.
        if is_mutation and ptrcls is not None:
            base_target = ptrcls.get_target(ctx.env.schema)
            assert base_target is not None
            if ptr_target.assignment_castable_to(base_target,
                                                 schema=ctx.env.schema):
                # Force assignment casts if the target type is not a
                # subclass of the base type and the cast is not to an
                # object type.
                if not (base_target.is_object_type()
                        or ptr_target.issubclass(ctx.env.schema, base_target)):
                    qlexpr = astutils.ensure_qlstmt(
                        qlast.TypeCast(
                            type=astutils.type_to_ql_typeref(
                                base_target, schema=ctx.env.schema),
                            expr=compexpr,
                        ))
                    ptr_target = base_target

            else:
                expected = [
                    repr(str(base_target.get_displayname(ctx.env.schema)))
                ]

                ercls: Type[errors.EdgeDBError]
                if ptrcls.is_property(ctx.env.schema):
                    ercls = errors.InvalidPropertyTargetError
                else:
                    ercls = errors.InvalidLinkTargetError

                ptr_vn = ptrcls.get_verbosename(ctx.env.schema,
                                                with_parent=True)

                raise ercls(
                    f'invalid target for {ptr_vn}: '
                    f'{str(ptr_target.get_displayname(ctx.env.schema))!r} '
                    f'(expecting {" or ".join(expected)})')

    if qlexpr is not None or ptrcls is None:
        if is_linkprop:
            # Proper checking was done when is_linkprop is defined.
            assert view_rptr is not None
            src_scls = view_rptr.ptrcls
        else:
            src_scls = view_scls

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

        if base_ptrcls is not None:
            derive_from = base_ptrcls
        else:
            derive_from = base

        derived_name = schemactx.derive_view_name(
            base_ptrcls,
            derived_name_base=ptr_name,
            derived_name_quals=[src_scls.get_name(ctx.env.schema)],
            ctx=ctx)

        existing = ctx.env.schema.get(derived_name, None)
        if existing is not None:
            existing_target = existing.get_target(ctx.env.schema)
            if ptr_target == existing_target:
                ptrcls = existing
            elif ptr_target.implicitly_castable_to(existing_target,
                                                   ctx.env.schema):
                ctx.env.schema = existing.set_target(ctx.env.schema,
                                                     ptr_target)
                ptrcls = existing
            else:
                target_rptr_set = (ptr_target.get_rptr(ctx.env.schema)
                                   is not None)

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

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

                if target_rptr_set:
                    ctx.env.schema = ptr_target.set_field_value(
                        ctx.env.schema,
                        'rptr',
                        ptrcls,
                    )
        else:
            ptrcls = schemactx.derive_ptr(derive_from,
                                          src_scls,
                                          ptr_target,
                                          is_insert=is_insert,
                                          is_update=is_update,
                                          derived_name=derived_name,
                                          ctx=ctx)

    elif ptrcls.get_target(ctx.env.schema) != ptr_target:
        ctx.env.schema = ptrcls.set_target(ctx.env.schema, ptr_target)

    assert ptrcls is not None

    if qlexpr is None:
        # This is not a computable, just a pointer
        # to a nested shape.  Have it reuse the original
        # pointer name so that in `Foo.ptr.name` and
        # `Foo { ptr: {name}}` are the same path.
        path_id_name = base_ptrcls.get_name(ctx.env.schema)
        ctx.env.schema = ptrcls.set_field_value(ctx.env.schema, 'path_id_name',
                                                path_id_name)

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

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

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

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

    if ptrcls.is_protected_pointer(ctx.env.schema) and qlexpr is not None:
        ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
        if is_polymorphic:
            msg = (f'cannot access {ptrcls_sn.name} on a polymorphic '
                   f'shape element')
        else:
            msg = f'cannot assign to {ptrcls_sn.name}'
        raise errors.QueryError(msg, context=shape_el.context)

    return ptrcls
예제 #25
0
파일: casts.py 프로젝트: stjordanis/edgedb
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:
        with ctx.new() as subctx:
            subctx.anchors = subctx.anchors.copy()
            source_path = subctx.create_anchor(ir_set, 'a')

            unpacked = qlast.FunctionCall(
                func=('__std__', 'array_unpack'),
                args=[source_path],
            )

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

            enumerated_ref = subctx.create_anchor(enumerated, 'e')

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

            # Force the elements to be correlated with whatever the
            # anchor was. (Doing it this way ensures a NULL check,
            # and just registering it in the scope would not.)
            correlated_elements = astutils.extend_path(
                qlast.Tuple(elements=[source_path, elements]), '1'
            )

            if el_type.contains_json(subctx.env.schema):
                subctx.inhibit_implicit_limit = True

            array_ir = dispatch.compile(correlated_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