Пример #1
0
def compile_path(expr: qlast.Path, *, ctx: context.ContextLevel) -> irast.Set:
    """Create an ir.Set representing the given EdgeQL path expression."""
    anchors = ctx.anchors

    path_tip = None

    if expr.partial:
        if ctx.partial_path_prefix is not None:
            path_tip = ctx.partial_path_prefix
        else:
            raise errors.QueryError('could not resolve partial path ',
                                    context=expr.context)

    extra_scopes = {}
    computables = []
    path_sets = []

    for i, step in enumerate(expr.steps):
        if isinstance(step, qlast.Source):
            # 'self' can only appear as the starting path label
            # syntactically and is a known anchor
            try:
                path_tip = anchors[step.__class__]
            except KeyError:
                path_tip = anchors['__source__']

        elif isinstance(step, qlast.Subject):
            # '__subject__' can only appear as the starting path label
            # syntactically and is a known anchor
            try:
                path_tip = anchors[step.__class__]
            except KeyError:
                path_tip = anchors['__subject__']

        elif isinstance(step, qlast.ObjectRef):
            if i > 0:  # pragma: no cover
                raise RuntimeError(
                    'unexpected ObjectRef as a non-first path item')

            refnode = None

            if not step.module and step.name not in ctx.aliased_views:
                # Check if the starting path label is a known anchor
                refnode = anchors.get(step.name)

            if refnode is not None:
                path_tip = new_set_from_set(refnode,
                                            preserve_scope_ns=True,
                                            ctx=ctx)
            else:
                stype = schemactx.get_schema_type(
                    step, item_types=(s_objtypes.ObjectType, ), ctx=ctx)

                if (stype.get_view_type(ctx.env.schema) is not None and
                        stype.get_name(ctx.env.schema) not in ctx.view_nodes):
                    # This is a schema-level view, as opposed to
                    # a WITH-block or inline alias view.
                    stype = stmtctx.declare_view_from_schema(stype, ctx=ctx)

                view_set = ctx.view_sets.get(stype)
                if view_set is not None:
                    path_tip = new_set_from_set(view_set, ctx=ctx)
                    path_scope = ctx.path_scope_map.get(view_set)
                    extra_scopes[path_tip] = path_scope.copy()
                else:
                    path_tip = class_set(stype, ctx=ctx)

                view_scls = ctx.class_view_overrides.get(stype.id)
                if (view_scls is not None
                        and view_scls != get_set_type(path_tip, ctx=ctx)):
                    path_tip = ensure_set(path_tip,
                                          type_override=view_scls,
                                          ctx=ctx)

        elif isinstance(step, qlast.Ptr):
            # Pointer traversal step
            ptr_expr = step
            direction = (ptr_expr.direction
                         or s_pointers.PointerDirection.Outbound)
            ptr_name = ptr_expr.ptr.name

            if ptr_expr.type == 'property':
                # Link property reference; the source is the
                # link immediately preceding this step in the path.
                source = irtyputils.ptrcls_from_ptrref(path_tip.rptr.ptrref,
                                                       schema=ctx.env.schema)
            else:
                source = get_set_type(path_tip, ctx=ctx)

            with ctx.newscope(fenced=True, temporary=True) as subctx:
                if isinstance(source, s_abc.Tuple):
                    path_tip = tuple_indirection_set(
                        path_tip,
                        source=source,
                        ptr_name=ptr_name,
                        source_context=step.context,
                        ctx=subctx)

                else:
                    path_tip = ptr_step_set(path_tip,
                                            source=source,
                                            ptr_name=ptr_name,
                                            direction=direction,
                                            ignore_computable=True,
                                            source_context=step.context,
                                            ctx=subctx)

                    ptrcls = irtyputils.ptrcls_from_ptrref(
                        path_tip.rptr.ptrref, schema=ctx.env.schema)
                    if _is_computable_ptr(ptrcls, ctx=ctx):
                        computables.append(path_tip)

        elif isinstance(step, qlast.TypeIndirection):
            arg_type = inference.infer_type(path_tip, ctx.env)
            if not isinstance(arg_type, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'invalid type filter operand: '
                    f'{arg_type.get_displayname(ctx.env.schema)} '
                    f'is not an object type',
                    context=step.context)

            typ = schemactx.get_schema_type(step.type.maintype, ctx=ctx)
            if not isinstance(typ, s_objtypes.ObjectType):
                raise errors.QueryError(
                    f'invalid type filter operand: '
                    f'{typ.get_displayname(ctx.env.schema)} is not '
                    f'an object type',
                    context=step.type.context)

            # The expression already of the desired type, elide
            # the indirection.
            if arg_type != typ:
                path_tip = class_indirection_set(path_tip,
                                                 typ,
                                                 optional=False,
                                                 ctx=ctx)

        else:
            # Arbitrary expression
            if i > 0:  # pragma: no cover
                raise RuntimeError(
                    'unexpected expression as a non-first path item')

            with ctx.newscope(fenced=True, temporary=True) as subctx:
                path_tip = ensure_set(dispatch.compile(step, ctx=subctx),
                                      ctx=subctx)

                if path_tip.path_id.is_type_indirection_path():
                    scope_set = path_tip.rptr.source
                else:
                    scope_set = path_tip

                extra_scopes[scope_set] = subctx.path_scope

        for key_path_id in path_tip.path_id.iter_weak_namespace_prefixes():
            mapped = ctx.view_map.get(key_path_id)
            if mapped is not None:
                path_tip = new_set(path_id=mapped.path_id,
                                   stype=get_set_type(path_tip, ctx=ctx),
                                   expr=mapped.expr,
                                   rptr=mapped.rptr,
                                   ctx=ctx)
                break

        if pathctx.path_is_banned(path_tip.path_id, ctx=ctx):
            dname = stype.get_displayname(ctx.env.schema)
            raise errors.QueryError(
                f'invalid reference to {dname}: '
                f'self-referencing INSERTs are not allowed',
                hint=(f'Use DETACHED if you meant to refer to an '
                      f'uncorrelated {dname} set'),
                context=step.context,
            )

        path_sets.append(path_tip)

    path_tip.context = expr.context
    pathctx.register_set_in_scope(path_tip, ctx=ctx)

    for ir_set in computables:
        scope = ctx.path_scope.find_descendant(ir_set.path_id)
        if scope is None:
            # The path is already in the scope, no point
            # in recompiling the computable expression.
            continue

        with ctx.new() as subctx:
            subctx.path_scope = scope
            comp_ir_set = computable_ptr_set(ir_set.rptr, ctx=subctx)
            i = path_sets.index(ir_set)
            if i != len(path_sets) - 1:
                path_sets[i + 1].rptr.source = comp_ir_set
            else:
                path_tip = comp_ir_set
            path_sets[i] = comp_ir_set

    for ir_set, scope in extra_scopes.items():
        node = ctx.path_scope.find_descendant(ir_set.path_id)
        if node is None:
            # The path portion not being a descendant means
            # that is is already present in the scope above us,
            # along with the view scope.
            continue

        fuse_scope_branch(ir_set, node, scope, ctx=ctx)
        if ir_set.path_scope_id is None:
            pathctx.assign_set_scope(ir_set, node, ctx=ctx)

    return path_tip
Пример #2
0
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 = irtyputils.ptrcls_from_ptrref(rptr.ptrref, schema=ctx.env.schema)
    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 ptrcls.derived_from.
    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:
            schema = ctx.env.schema
            source_rptrref = source_set.rptr.ptrref
            source_rptrcls = irtyputils.ptrcls_from_ptrref(source_rptrref,
                                                           schema=schema)
            derived_from = source_rptrcls.get_derived_from(schema)
            if (derived_from is not None and not derived_from.generic(schema)
                    and derived_from.get_derived_from(schema) is not None
                    and ptrcls.is_link_property(schema)):
                source_set.rptr.ptrref = irtyputils.ptrref_from_ptrcls(
                    source_ref=source_rptrref.dir_source,
                    target_ref=source_rptrref.dir_target,
                    direction=source_rptrref.direction,
                    parent_ptr=source_rptrref.parent_ptr,
                    ptrcls=derived_from,
                    schema=schema,
                )

                stmtctx.ensure_ptrref_cardinality(derived_from,
                                                  source_set.rptr.ptrref,
                                                  ctx=ctx)

    try:
        qlexpr, qlctx, inner_source_path_id, path_id_ns = \
            ctx.source_map[ptrcls]
    except KeyError:
        ptrcls_default = ptrcls.get_default(ctx.env.schema)
        if not ptrcls_default:
            ptrcls_sn = ptrcls.get_shortname(ctx.env.schema)
            raise ValueError(f'{ptrcls_sn!r} is not a computable pointer')

        qlexpr = qlparser.parse(ptrcls_default.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)
        if not target_scls.is_object_type():
            qlexpr = qlast.TypeCast(
                type=astutils.type_to_ql_typeref(target_scls,
                                                 schema=ctx.env.schema),
                expr=qlexpr,
            )
        qlexpr = astutils.ensure_qlstmt(qlexpr)
        qlctx = None
        inner_source_path_id = None
        path_id_ns = None

    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:
        source_path_id = rptr.target.path_id.src_path()

    result_stype = ptrcls.get_target(ctx.env.schema)
    result_path_id = pathctx.extend_path_id(
        source_path_id,
        ptrcls=ptrcls,
        direction=s_pointers.PointerDirection.Outbound,
        target=result_stype,
        ns=ctx.path_id_namespace,
        ctx=ctx)

    with newctx() as subctx:
        subctx.view_scls = result_stype
        subctx.view_rptr = context.ViewRPtr(source_scls,
                                            ptrcls=ptrcls,
                                            rptr=rptr)
        subctx.anchors[qlast.Source] = source_set
        subctx.empty_result_type_hint = ptrcls.get_target(ctx.env.schema)
        subctx.partial_path_prefix = source_set

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

        comp_ir_set = dispatch.compile(qlexpr, 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 and not pending_cardinality.from_parent:
        stmtctx.get_pointer_cardinality_later(
            ptrcls=ptrcls,
            irexpr=comp_ir_set_copy,
            specified_card=pending_cardinality.specified_cardinality,
            source_ctx=pending_cardinality.source_ctx,
            ctx=ctx)

    def _check_cardinality(ctx):
        if ptrcls.singular(ctx.env.schema):
            stmtctx.enforce_singleton_now(comp_ir_set_copy, ctx=ctx)

    stmtctx.at_stmt_fini(_check_cardinality, 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
Пример #3
0
def compile_insert_unless_conflict_on(
    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(
            'UNLESS CONFLICT argument must be a property',
            context=constraint_spec.context,
        )

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

    schema = ctx.env.schema
    schema, typ = typeutils.ir_typeref_to_type(schema, stmt.subject.typeref)
    assert isinstance(typ, s_objtypes.ObjectType)
    real_typ = typ.get_nearest_non_derived_parent(schema)

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

    ptr = ptr.get_nearest_non_derived_parent(schema)
    ptr_card = ptr.get_cardinality(schema)
    if not ptr_card.is_single():
        raise errors.QueryError(
            'UNLESS CONFLICT property must be a SINGLE property',
            context=constraint_spec.context,
        )

    exclusive_constr = schema.get('std::exclusive', type=s_constr.Constraint)
    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(
            'UNLESS 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
    ds = {field_name.name: (ptr, ex_cnstrs)}
    select_ir = compile_insert_unless_conflict_select(
        stmt, insert_subject, real_typ, constrs=ds, obj_constrs=[],
        parser_context=stmt.context, ctx=ctx)

    # Compile an else branch
    else_ir = None
    if else_branch:
        # 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)

    return irast.OnConflictClause(
        constraint=irast.ConstraintRef(
            id=ex_cnstrs[0].id, module_id=module_id),
        select_ir=select_ir,
        else_ir=else_ir
    )
Пример #4
0
    def _get_ref_storage_info(cls, schema, refs):
        link_biased = {}
        objtype_biased = {}

        ref_ptrs = {}
        for ref in refs:
            rptr = ref.rptr
            if rptr is not None:
                ptrref = ref.rptr.ptrref
                ptr = irtyputils.ptrcls_from_ptrref(ptrref, schema=schema)
                if ptr.is_link_property(schema):
                    srcref = ref.rptr.source.rptr.ptrref
                    src = irtyputils.ptrcls_from_ptrref(srcref, schema=schema)
                    if src.get_is_derived(schema):
                        # This specialized pointer was derived specifically
                        # for the purposes of constraint expr compilation.
                        src = src.get_bases(schema).first(schema)
                else:
                    src = irtyputils.ir_typeref_to_type(
                        schema, ref.rptr.source.typeref)
                ref_ptrs[ref] = (ptr, src)

        for ref, (ptr, src) in ref_ptrs.items():
            ptr_info = types.get_pointer_storage_info(ptr,
                                                      source=src,
                                                      resolve_type=False,
                                                      schema=schema)

            # See if any of the refs are hosted in pointer tables and others
            # are not...
            if ptr_info.table_type == 'link':
                link_biased[ref] = ptr_info
            else:
                objtype_biased[ref] = ptr_info

            if link_biased and objtype_biased:
                break

        if link_biased and objtype_biased:
            for ref in objtype_biased.copy():
                ptr, src = ref_ptrs[ref]
                ptr_info = types.get_pointer_storage_info(ptr,
                                                          source=src,
                                                          resolve_type=False,
                                                          link_bias=True,
                                                          schema=schema)

                if ptr_info.table_type == 'link':
                    link_biased[ref] = ptr_info
                    objtype_biased.pop(ref)

        ref_tables = {}

        for ref, ptr_info in itertools.chain(objtype_biased.items(),
                                             link_biased.items()):
            ptr, src = ref_ptrs[ref]

            try:
                ref_tables[ptr_info.table_name].append(
                    (ref, ptr, src, ptr_info))
            except KeyError:
                ref_tables[ptr_info.table_name] = [(ref, ptr, src, ptr_info)]

        return ref_tables
Пример #5
0
    def _parse_computable(
        self,
        expr: qlast.Base,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]:
        from edb.ir import ast as irast
        from edb.ir import typeutils as irtyputils
        from edb.schema import objtypes as s_objtypes

        # "source" attribute is set automatically as a refdict back-attr
        parent_ctx = self.get_referrer_context(context)
        assert parent_ctx is not None
        source_name = parent_ctx.op.classname

        source = schema.get(source_name, type=s_objtypes.ObjectType)

        ptr_name = self.get_displayname()
        expression = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr, schema, context.modaliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=context.modaliases,
                anchors={qlast.Source().name: source},
                path_prefix_anchor=qlast.Source().name,
                singletons=frozenset([source]),
                in_ddl_context_name=f'computable {ptr_name!r}',
            ),
        )

        assert isinstance(expression.irast, irast.Statement)
        base = None
        target = expression.irast.stype
        result_expr = expression.irast.expr

        # Process a computable pointer which potentially could be an
        # aliased link that should inherit link properties.
        if (isinstance(result_expr, irast.Set)
                and result_expr.rptr is not None):
            expr_rptr = result_expr.rptr
            while isinstance(expr_rptr, irast.TypeIntersectionPointer):
                expr_rptr = expr_rptr.source.rptr

            is_ptr_alias = (
                expr_rptr.direction is PointerDirection.Outbound
            )

            if is_ptr_alias:
                new_schema, base = irtyputils.ptrcls_from_ptrref(
                    expr_rptr.ptrref, schema=schema
                )
                # Only pointers coming from the same source as the
                # alias should be "inherited" (in order to preserve
                # link props). Random paths coming from other sources
                # get treated same as any other arbitrary expression
                # in a computable.
                if base.get_source(new_schema) != source:
                    base = None
                else:
                    schema = new_schema

        self.set_attribute_value('expr', expression)
        required, card = expression.irast.cardinality.to_schema_value()

        is_alter = (
            isinstance(self, sd.AlterObject)
            or (
                isinstance(self, sd.AlterObjectFragment)
                and isinstance(self.get_parent_op(context), sd.AlterObject)
            )
        )

        spec_required = self.get_attribute_value('required')
        if spec_required is None and is_alter:
            spec_required = self.scls.get_required(schema)
        spec_card = self.get_attribute_value('cardinality')
        if spec_card is None and is_alter:
            spec_card = self.scls.get_cardinality(schema)

        if spec_required and not required:
            srcctx = self.get_attribute_source_context('target')
            raise errors.SchemaDefinitionError(
                f'possibly an empty set returned by an '
                f'expression for the computable '
                f'{ptr_name!r} '
                f"declared as 'required'",
                context=srcctx
            )

        if (
            spec_card in {None, qltypes.SchemaCardinality.ONE} and
            card is not qltypes.SchemaCardinality.ONE
        ):
            srcctx = self.get_attribute_source_context('target')
            raise errors.SchemaDefinitionError(
                f'possibly more than one element returned by an '
                f'expression for the computable '
                f'{ptr_name!r} '
                f"declared as 'single'",
                context=srcctx
            )

        if spec_card is None:
            self.set_attribute_value('cardinality', card)

        if not spec_required:
            self.set_attribute_value('required', required)

        self.set_attribute_value('computable', True)

        return schema, target, base
Пример #6
0
def _get_shape_configuration(
        ir_set: irast.Set, *,
        rptr: typing.Optional[irast.Pointer]=None,
        parent_view_type: typing.Optional[s_types.ViewType]=None,
        ctx: context.ContextLevel) \
        -> typing.List[typing.Tuple[irast.Set, s_pointers.Pointer]]:

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

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

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

    if rptr is None:
        rptr = ir_set.rptr

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

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

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

    if link_view:
        sources.append(rptrcls)

    shape_ptrs = []

    id_present_in_shape = False

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

            shape_ptrs.append((path_tip, ptr))

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

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

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

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

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

    return shape_ptrs
Пример #7
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)
Пример #8
0
    def _parse_computable(
        self,
        expr: qlast.Base,
        schema: s_schema.Schema,
        context: sd.CommandContext,
    ) -> Tuple[s_schema.Schema, s_types.Type, Optional[PointerLike]]:
        from edb.ir import ast as irast
        from edb.ir import typeutils as irtyputils
        from edb.schema import objtypes as s_objtypes

        # "source" attribute is set automatically as a refdict back-attr
        parent_ctx = self.get_referrer_context(context)
        assert parent_ctx is not None
        source_name = parent_ctx.op.classname

        source = schema.get(source_name, type=s_objtypes.ObjectType)
        expression = s_expr.Expression.compiled(
            s_expr.Expression.from_ast(expr, schema, context.modaliases),
            schema=schema,
            options=qlcompiler.CompilerOptions(
                modaliases=context.modaliases,
                anchors={qlast.Source().name: source},
                path_prefix_anchor=qlast.Source().name,
                singletons=frozenset([source]),
            ),
        )

        assert isinstance(expression.irast, irast.Statement)
        base = None
        target = expression.irast.stype

        result_expr = expression.irast.expr.expr

        if (isinstance(result_expr, irast.SelectStmt)
                and result_expr.result.rptr is not None):
            expr_rptr = result_expr.result.rptr
            while isinstance(expr_rptr, irast.TypeIntersectionPointer):
                expr_rptr = expr_rptr.source.rptr

            is_ptr_alias = (expr_rptr.direction is PointerDirection.Outbound)

            if is_ptr_alias:
                schema, base = irtyputils.ptrcls_from_ptrref(expr_rptr.ptrref,
                                                             schema=schema)

        self.set_attribute_value('expr', expression)
        required, card = expression.irast.cardinality.to_schema_value()
        spec_required = self.get_attribute_value('required')
        spec_card = self.get_attribute_value('cardinality')

        # If cardinality was unspecified and the computable is not
        # required, use the inferred cardinality.
        if spec_card is None and not spec_required:
            self.set_attribute_value('required', required)
            self.set_attribute_value('cardinality', card)
        else:
            # Otherwise honor the spec, so no cardinality change, but check
            # that it's valid.

            if spec_card is None:
                # A computable link is marked explicitly as
                # "required", so we assume that omitted cardinality is
                # "single". Basically, to infer the cardinality both
                # cardinality-related qualifiers need to be omitted.
                spec_card = qltypes.SchemaCardinality.ONE

            if spec_required and not required:
                ptr_name = sn.shortname_from_fullname(
                    self.get_attribute_value('name')).name
                srcctx = self.get_attribute_source_context('target')
                raise errors.QueryError(
                    f'possibly an empty set returned by an '
                    f'expression for a computable '
                    f'{ptr_name!r} '
                    f"declared as 'required'",
                    context=srcctx)
            if (spec_card is qltypes.SchemaCardinality.ONE
                    and card != spec_card):
                ptr_name = sn.shortname_from_fullname(
                    self.get_attribute_value('name')).name
                srcctx = self.get_attribute_source_context('target')
                raise errors.QueryError(
                    f'possibly more than one element returned by an '
                    f'expression for a computable '
                    f'{ptr_name!r} '
                    f"declared as 'single'",
                    context=srcctx)

        self.set_attribute_value('computable', True)

        return schema, target, base
Пример #9
0
def handle_conditional_insert(
    expr: qlast.InsertQuery,
    rhs: irast.InsertStmt,
    lhs_set: Union[irast.Expr, irast.Set],
    *,
    ctx: context.ContextLevel,
) -> irast.ConstraintRef:
    def error(s: str) -> NoReturn:
        raise errors.QueryError(
            f'Invalid conditional INSERT statement: {s}',
            context=expr.context,
        )

    schema = ctx.env.schema

    if not isinstance(lhs_set, irast.Set):
        error("left hand side is not SELECT")
    lhs_set = irutils.unwrap_set(lhs_set)
    if not isinstance(lhs_set.expr, irast.SelectStmt):
        error("left hand side is not SELECT")
    lhs = lhs_set.expr

    if lhs.result.path_id != rhs.subject.path_id:
        error("types do not match")

    if lhs.where:
        filtered_ptrs = inference.cardinality.extract_filters(
            lhs.result,
            lhs.where,
            scope_tree=ctx.path_scope,
            singletons=(),
            env=ctx.env,
            strict=True)
    else:
        filtered_ptrs = None

    # TODO: Can we support some >1 cases?
    if not filtered_ptrs or len(filtered_ptrs) > 1:
        error("does not contain exactly one FILTER clause")
        return None

    exclusive_constr: s_constr.Constraint = schema.get('std::exclusive')

    shape_props = {}
    for shape_set, _ in rhs.subject.shape:
        # We need to go through to the base_ptr to get at the
        # underlying type (instead of the shape's subtype)
        base_ptr = shape_set.rptr.ptrref.base_ptr
        if (not isinstance(base_ptr, irast.PointerRef)
                or not isinstance(shape_set.expr, irast.SelectStmt)):
            continue
        schema, pptr = typeutils.ptrcls_from_ptrref(base_ptr, schema=schema)
        shape_props[pptr] = shape_set.expr.result, base_ptr

    ptr, ptr_set = filtered_ptrs[0]

    ptr = ptr.get_nearest_non_derived_parent(schema)
    if ptr not in shape_props:
        error("property in FILTER clause does not match INSERT")
    result, rptr = shape_props[ptr]

    if not simple_stmt_eq(ptr_set.expr, result.expr, schema):
        error("value in FILTER clause does not match INSERT")

    ex_cnstrs = [
        c for c in ptr.get_constraints(schema).objects(schema)
        if c.issubclass(schema, exclusive_constr)
        and not c.get_subjectexpr(schema)
    ]

    if len(ex_cnstrs) != 1 or not ptr.is_property(schema):
        error("FILTER is not on an exclusive property")

    ex_cnstr = ex_cnstrs[0]

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

    ctx.env.schema = schema
    return irast.ConstraintRef(id=ex_cnstr.id, module_id=module_id)
Пример #10
0
def compile_query_subject(expr: irast.Set,
                          *,
                          shape: typing.Optional[typing.List[
                              qlast.ShapeElement]] = None,
                          view_rptr: typing.Optional[context.ViewRPtr] = None,
                          view_name: typing.Optional[s_name.SchemaName] = None,
                          result_alias: typing.Optional[str] = None,
                          view_scls: typing.Optional[s_types.Type] = None,
                          compile_views: bool = True,
                          is_insert: bool = False,
                          is_update: bool = False,
                          ctx: context.ContextLevel) -> irast.Set:

    expr_stype = setgen.get_set_type(expr, ctx=ctx)
    expr_rptr = expr.rptr

    while isinstance(expr_rptr, irast.TypeIndirectionPointer):
        expr_rptr = expr_rptr.source.rptr

    is_ptr_alias = (view_rptr is not None and view_rptr.ptrcls is None
                    and view_rptr.ptrcls_name is not None
                    and expr_rptr is not None and
                    expr_rptr.direction is s_pointers.PointerDirection.Outbound
                    and (view_rptr.ptrcls_is_linkprop
                         == (expr_rptr.ptrref.parent_ptr is not None)))

    if is_ptr_alias:
        assert view_rptr is not None
        # We are inside an expression that defines a link alias in
        # the parent shape, ie. Spam { alias := Spam.bar }, so
        # `Spam.alias` should be a subclass of `Spam.bar` inheriting
        # its properties.
        view_rptr.base_ptrcls = irtyputils.ptrcls_from_ptrref(
            expr_rptr.ptrref, schema=ctx.env.schema)
        view_rptr.ptrcls_is_alias = True

    if (ctx.expr_exposed and viewgen.has_implicit_tid(
            expr_stype, is_mutation=is_insert or is_update, ctx=ctx)
            and shape is None and expr_stype not in ctx.env.view_shapes):
        # Force the subject to be compiled as a view if a __tid__
        # insertion is anticipated (the actual decision is taken
        # by the compile_view_shapes() flow).
        shape = []

    if shape is not None and view_scls is None:
        if (view_name is None and isinstance(result_alias, s_name.SchemaName)):
            view_name = result_alias

        view_scls = viewgen.process_view(stype=expr_stype,
                                         path_id=expr.path_id,
                                         elements=shape,
                                         view_rptr=view_rptr,
                                         view_name=view_name,
                                         is_insert=is_insert,
                                         is_update=is_update,
                                         ctx=ctx)

    if view_scls is not None:
        expr = setgen.ensure_set(expr, type_override=view_scls, ctx=ctx)
        expr_stype = view_scls

    if compile_views:
        rptr = view_rptr.rptr if view_rptr is not None else None
        viewgen.compile_view_shapes(expr, rptr=rptr, ctx=ctx)

    if (shape is not None or view_scls is not None) and len(expr.path_id) == 1:
        ctx.class_view_overrides[expr.path_id.target.id] = expr_stype

    return expr
Пример #11
0
def compile_query_subject(
        expr: irast.Set, *,
        shape: typing.Optional[typing.List[qlast.ShapeElement]]=None,
        view_rptr: typing.Optional[context.ViewRPtr]=None,
        view_name: typing.Optional[s_name.SchemaName]=None,
        result_alias: typing.Optional[str]=None,
        view_scls: typing.Optional[s_types.Type]=None,
        compile_views: bool=True,
        is_insert: bool=False,
        is_update: bool=False,
        ctx: context.ContextLevel) -> irast.Set:

    expr_stype = setgen.get_set_type(expr, ctx=ctx)
    expr_rptr = expr.rptr

    is_ptr_alias = (
        view_rptr is not None
        and view_rptr.ptrcls is None
        and view_rptr.ptrcls_name is not None
        and expr_rptr is not None
        and (
            view_rptr.ptrcls_is_linkprop
            == (expr_rptr.ptrref.parent_ptr is not None)
        )
    )

    if is_ptr_alias:
        # We are inside an expression that defines a link alias in
        # the parent shape, ie. Spam { alias := Spam.bar }, so
        # `Spam.alias` should be a subclass of `Spam.bar` inheriting
        # its properties.
        rptr = expr_rptr
        # Unwind type indirections first.
        while isinstance(rptr, irast.TypeIndirectionPointer):
            rptr = rptr.source.rptr
        view_rptr.base_ptrcls = irtyputils.ptrcls_from_ptrref(
            rptr.ptrref, schema=ctx.env.schema)

    if shape is not None and view_scls is None:
        if (view_name is None and
                isinstance(result_alias, s_name.SchemaName)):
            view_name = result_alias

        view_scls = viewgen.process_view(
            stype=expr_stype, path_id=expr.path_id,
            elements=shape, view_rptr=view_rptr,
            view_name=view_name, is_insert=is_insert,
            is_update=is_update, ctx=ctx)

    if view_scls is not None:
        expr = setgen.ensure_set(expr, type_override=view_scls, ctx=ctx)
        expr_stype = view_scls

    if compile_views:
        rptr = view_rptr.rptr if view_rptr is not None else None
        viewgen.compile_view_shapes(expr, rptr=rptr, ctx=ctx)

    if (shape is not None or view_scls is not None) and len(expr.path_id) == 1:
        ctx.class_view_overrides[expr.path_id.target.id] = expr_stype

    return expr