Exemplo n.º 1
0
def compile_Coalesce(expr: qlast.Base, *,
                     ctx: context.ContextLevel) -> irast.Base:
    if all(isinstance(a, qlast.Set) and not a.elements for a in expr.args):
        return irutils.new_empty_set(ctx.schema, alias=ctx.aliases.get('e'))

    with ctx.newscope() as newctx:
        leftmost_arg = larg = setgen.ensure_set(dispatch.compile(expr.args[0],
                                                                 ctx=newctx),
                                                ctx=newctx)

        for rarg_ql in expr.args[1:]:
            with newctx.new() as nestedscopectx:
                with nestedscopectx.newscope(fenced=True) as fencectx:
                    rarg = setgen.scoped_set(dispatch.compile(rarg_ql,
                                                              ctx=fencectx),
                                             ctx=fencectx)

                coalesce = irast.Coalesce(left=larg, right=rarg)
                larg = setgen.generated_set(coalesce, ctx=nestedscopectx)

        # Make sure any empty set types are properly resolved
        # before entering them into the scope tree.
        irutils.infer_type(larg, schema=ctx.schema)

        pathctx.register_set_in_scope(leftmost_arg, ctx=ctx)
        pathctx.mark_path_as_optional(leftmost_arg.path_id, ctx=ctx)

    return larg
Exemplo n.º 2
0
def compile_equivalence_op(expr: qlast.BinOp, *,
                           ctx: context.ContextLevel) -> irast.EquivalenceOp:
    # A ?= B ≣ EQUIV(OPTIONAL any A, OPTIONAL any B) -> std::bool
    # Definition:
    #   | {a = b | ∀ (a, b) ∈ A ⨯ B}, iff A != ∅ ∧ B != ∅
    #   | {True}, iff A = B = ∅
    #   | {False}, iff A != ∅ ∧ B = ∅
    #   | {False}, iff A = ∅ ∧ B != ∅
    #
    # A ?!= B ≣ NEQUIV(OPTIONAL any A, OPTIONAL any B) -> std::bool
    # Definition:
    #   | {a != b | ∀ (a, b) ∈ A ⨯ B}, iff A != ∅ ∧ B != ∅
    #   | {False}, iff A = B = ∅
    #   | {True}, iff A != ∅ ∧ B = ∅
    #   | {True}, iff A = ∅ ∧ B != ∅
    left = setgen.ensure_set(dispatch.compile(expr.left, ctx=ctx), ctx=ctx)
    right = setgen.ensure_set(dispatch.compile(expr.right, ctx=ctx), ctx=ctx)
    result = irast.EquivalenceOp(left=left, right=right, op=expr.op)

    # Make sure any empty set types are properly resolved
    # before entering them into the scope tree.
    irutils.infer_type(result, schema=ctx.schema)

    pathctx.register_set_in_scope(left, ctx=ctx)
    pathctx.mark_path_as_optional(left.path_id, ctx=ctx)
    pathctx.register_set_in_scope(right, ctx=ctx)
    pathctx.mark_path_as_optional(right.path_id, ctx=ctx)

    return result
Exemplo n.º 3
0
def compile_IfElse(expr: qlast.IfElse, *,
                   ctx: context.ContextLevel) -> irast.Base:

    condition = setgen.ensure_set(dispatch.compile(expr.condition, ctx=ctx),
                                  ctx=ctx)

    ql_if_expr = expr.if_expr
    ql_else_expr = expr.else_expr

    with ctx.newscope(fenced=True) as scopectx:
        if_expr = dispatch.compile(ql_if_expr, ctx=scopectx)

    with ctx.newscope(fenced=True) as scopectx:
        else_expr = dispatch.compile(ql_else_expr, ctx=scopectx)

    if_expr_type = irutils.infer_type(if_expr, ctx.schema)
    else_expr_type = irutils.infer_type(else_expr, ctx.schema)

    result = s_utils.get_class_nearest_common_ancestor(
        [if_expr_type, else_expr_type])

    if result is None:
        raise errors.EdgeQLError(
            'if/else clauses must be of related types, got: {}/{}'.format(
                if_expr_type.name, else_expr_type.name),
            context=expr.context)

    return setgen.generated_set(irast.IfElseExpr(if_expr=if_expr,
                                                 else_expr=else_expr,
                                                 condition=condition),
                                ctx=ctx)
Exemplo n.º 4
0
def fini_expression(ir: irast.Base, *,
                    ctx: context.ContextLevel) -> irast.Command:
    for ir_set in ctx.all_sets:
        if ir_set.path_id.namespace:
            ir_set.path_id = ir_set.path_id.strip_weak_namespaces()

    if isinstance(ir, irast.Command):
        # IR is already a Command
        return ir

    if ctx.path_scope is not None:
        # Simple expressions have no scope.
        for node in ctx.path_scope.get_all_path_nodes(include_subpaths=True):
            if node.path_id.namespace:
                node.path_id = node.path_id.strip_weak_namespaces()

        cardinality = pathctx.infer_cardinality(ir, ctx=ctx)
    else:
        cardinality = irast.Cardinality.ONE

    result = irast.Statement(
        expr=ir,
        params=ctx.arguments,
        views=ctx.view_nodes,
        source_map=ctx.source_map,
        scope_tree=ctx.path_scope,
        cardinality=cardinality,
        view_shapes=ctx.class_shapes,
    )
    irutils.infer_type(result, schema=ctx.schema)
    return result
Exemplo n.º 5
0
def _cast_expr(ql_type: qlast.TypeName, ir_expr: irast.Base, *,
               source_context: parsing.ParserContext,
               ctx: context.ContextLevel) -> irast.Base:
    try:
        orig_type = irutils.infer_type(ir_expr, ctx.schema)
    except errors.EdgeQLError:
        # It is possible that the source expression is unresolved
        # if the expr is an empty set (or a coalesce of empty sets).
        orig_type = None

    if isinstance(orig_type, s_types.Tuple):
        # For tuple-to-tuple casts we generate a new tuple
        # to simplify things on sqlgen side.
        new_type = typegen.ql_typeref_to_type(ql_type, ctx=ctx)
        if not isinstance(new_type, s_types.Tuple):
            raise errors.EdgeQLError(f'cannot cast tuple to {new_type.name}',
                                     context=source_context)

        if len(orig_type.element_types) != len(new_type.element_types):
            raise errors.EdgeQLError(
                f'cannot cast to {new_type.name}: '
                f'number of elements is not the same',
                context=source_context)

        new_names = list(new_type.element_types)

        elements = []
        for i, n in enumerate(orig_type.element_types):
            val = setgen.generated_set(irast.TupleIndirection(expr=ir_expr,
                                                              name=n),
                                       ctx=ctx)
            val.path_id = irutils.tuple_indirection_path_id(
                ir_expr.path_id, n, orig_type.element_types[n])

            val_type = irutils.infer_type(val, ctx.schema)
            new_el_name = new_names[i]
            if val_type != new_type.element_types[new_el_name]:
                # Element cast
                val = _cast_expr(ql_type.subtypes[i],
                                 val,
                                 ctx=ctx,
                                 source_context=source_context)

            elements.append(irast.TupleElement(name=new_el_name, val=val))

        return irast.Tuple(named=new_type.named, elements=elements)

    elif isinstance(ir_expr, irast.EmptySet):
        # For the common case of casting an empty set, we simply
        # generate a new EmptySet node of the requested type.
        scls = typegen.ql_typeref_to_type(ql_type, ctx=ctx)
        return irutils.new_empty_set(ctx.schema,
                                     scls=scls,
                                     alias=ir_expr.path_id.target.name.name)

    else:
        typ = typegen.ql_typeref_to_ir_typeref(ql_type, ctx=ctx)
        return setgen.ensure_set(irast.TypeCast(expr=ir_expr, type=typ),
                                 ctx=ctx)
Exemplo n.º 6
0
    def normalize_constraint_expr(cls,
                                  schema,
                                  module_aliases,
                                  expr,
                                  *,
                                  subject=None,
                                  constraint,
                                  expr_context=None,
                                  enforce_boolean=False):
        from edb.lang.ir import utils as irutils

        if subject is None:
            subject = cls._dummy_subject()

        edgeql_tree, ir_result = cls._normalize_constraint_expr(
            schema, module_aliases, expr, subject)

        if enforce_boolean:
            bool_t = schema.get('std::bool')
            expr_type = irutils.infer_type(ir_result, schema)
            if not expr_type.issubclass(bool_t):
                raise s_errors.SchemaDefinitionError(
                    f'{constraint.displayname} constraint expression expected '
                    f'to return a bool value, got {expr_type.name.name!r}',
                    context=expr_context)

        expr = edgeql.generate_source(edgeql_tree, pretty=False)
        # XXX: check that expr has boolean result
        return expr
Exemplo n.º 7
0
def fini_stmt(
        irstmt: irast.Base, qlstmt: qlast.Statement, *,
        ctx: context.ContextLevel,
        parent_ctx: context.ContextLevel) -> irast.Set:
    irstmt.cardinality = qlstmt.cardinality

    view_name = parent_ctx.toplevel_result_view_name
    t = irutils.infer_type(irstmt, ctx.schema)

    if t.name == view_name:
        # The view statement did contain a view declaration and
        # generated a view class with the requested name.
        view = t
        path_id = pathctx.get_path_id(view, ctx=parent_ctx)
    elif view_name is not None:
        # The view statement did _not_ contain a view declaration,
        # but we still want the correct path_id.
        view = schemactx.derive_view(t, derived_name=view_name, ctx=parent_ctx)
        path_id = pathctx.get_path_id(view, ctx=parent_ctx)
    else:
        view = None
        path_id = None

    result = setgen.scoped_set(irstmt, path_id=path_id, ctx=ctx)

    if view is not None:
        parent_ctx.view_sets[view] = result
        result.scls = view

    return result
Exemplo n.º 8
0
def compile_UpdateQuery(
        expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base:
    with ctx.subquery() as ictx:
        stmt = irast.UpdateStmt()
        init_stmt(stmt, expr, ctx=ictx, parent_ctx=ctx)

        subject = dispatch.compile(expr.subject, ctx=ictx)
        subj_type = irutils.infer_type(subject, ictx.schema)
        if not isinstance(subj_type, s_objtypes.ObjectType):
            raise errors.EdgeQLError(
                f'cannot update non-ObjectType objects',
                context=expr.subject.context
            )

        stmt.subject = compile_query_subject(
            subject,
            shape=expr.shape,
            view_rptr=ctx.view_rptr,
            compile_views=True,
            result_alias=expr.subject_alias,
            is_update=True,
            ctx=ictx)

        stmt.result = setgen.class_set(
            stmt.subject.scls.material_type(),
            path_id=stmt.subject.path_id, ctx=ctx)

        stmt.where = clauses.compile_where_clause(
            expr.where, ctx=ictx)

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

    return result
Exemplo n.º 9
0
def compile_DeleteQuery(
        expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base:
    with ctx.subquery() as ictx:
        stmt = irast.DeleteStmt()
        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:
            subject = setgen.scoped_set(
                dispatch.compile(expr.subject, ctx=scopectx), ctx=scopectx)

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

        stmt.subject = compile_query_subject(
            subject, shape=None, result_alias=expr.subject_alias, ctx=ictx)

        stmt.result = setgen.class_set(
            stmt.subject.scls.material_type(),
            path_id=stmt.subject.path_id, ctx=ctx)

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

    return result
Exemplo n.º 10
0
def compile_UnaryOp(expr: qlast.Base, *,
                    ctx: context.ContextLevel) -> irast.Set:
    if expr.op == qlast.DISTINCT:
        return compile_distinct_op(expr, ctx=ctx)

    operand = dispatch.compile(expr.operand, ctx=ctx)
    if astutils.is_exists_expr_set(operand):
        operand.expr.negated = not operand.expr.negated
        return operand

    unop = irast.UnaryOp(expr=operand, op=expr.op)
    result_type = irutils.infer_type(unop, ctx.schema)

    real_t = ctx.schema.get('std::anyreal')

    if (isinstance(operand.expr, irast.Constant)
            and result_type.issubclass(real_t)):
        # Fold the operation to constant if possible
        if expr.op == ast.ops.UMINUS:
            return setgen.ensure_set(irast.Constant(value=-operand.expr.value,
                                                    type=result_type),
                                     ctx=ctx)
        elif expr.op == ast.ops.UPLUS:
            return operand

    return setgen.generated_set(unop, ctx=ctx)
Exemplo n.º 11
0
def compile_Array(expr: qlast.Base, *,
                  ctx: context.ContextLevel) -> irast.Base:
    elements = [dispatch.compile(e, ctx=ctx) for e in expr.elements]
    # check that none of the elements are themselves arrays
    for el, expr_el in zip(elements, expr.elements):
        if isinstance(irutils.infer_type(el, ctx.schema), s_types.Array):
            raise errors.EdgeQLError(f'nested arrays are not supported',
                                     context=expr_el.context)
    return setgen.generated_set(irast.Array(elements=elements), ctx=ctx)
Exemplo n.º 12
0
def try_fold_arithmetic_binop(
        op: ast.ops.Operator, left: irast.Set, right: irast.Set, *,
        ctx: context.ContextLevel) -> typing.Optional[irast.Set]:
    """Try folding an arithmetic expr into a constant."""
    schema = ctx.schema

    real_t = schema.get('std::anyreal')
    float_t = schema.get('std::anyfloat')
    int_t = schema.get('std::anyint')

    left_type = irutils.infer_type(left, schema)
    right_type = irutils.infer_type(right, schema)

    if not left_type.issubclass(real_t) or not right_type.issubclass(real_t):
        return

    result_type = left_type
    if right_type.issubclass(float_t):
        result_type = right_type

    left = left.expr
    right = right.expr

    if op == ast.ops.ADD:
        value = left.value + right.value
    elif op == ast.ops.SUB:
        value = left.value - right.value
    elif op == ast.ops.MUL:
        value = left.value * right.value
    elif op == ast.ops.DIV:
        if left_type.issubclass(int_t) and right_type.issubclass(int_t):
            value = left.value // right.value
        else:
            value = left.value / right.value
    elif op == ast.ops.POW:
        value = left.value**right.value
    elif op == ast.ops.MOD:
        value = left.value % right.value
    else:
        value = None

    if value is not None:
        return setgen.ensure_set(irast.Constant(value=value, type=result_type),
                                 ctx=ctx)
Exemplo n.º 13
0
def try_fold_binop(binop: irast.BinOp, *,
                   ctx: context.ContextLevel) -> typing.Optional[irast.Set]:
    """Try folding a binary operator expression."""
    schema = ctx.schema
    real_t = schema.get('std::anyreal')

    result_type = irutils.infer_type(binop, schema)
    folded = None

    left = binop.left
    right = binop.right
    op = binop.op

    if (isinstance(left.expr, irast.Constant)
            and isinstance(right.expr, irast.Constant)):
        # Left and right nodes are constants.
        if isinstance(op, ast.ops.ComparisonOperator):
            folded = try_fold_comparison_binop(op, left, right, ctx=ctx)

        elif result_type.issubclass(real_t):
            folded = try_fold_arithmetic_binop(op, left, right, ctx=ctx)

    elif op in {ast.ops.ADD, ast.ops.MUL}:
        # Let's check if we have (CONST + (OTHER_CONST + X))
        # tree, which can be optimized to ((CONST + OTHER_CONST) + X)

        my_const = left
        other_binop = right
        if isinstance(right.expr, irast.Constant):
            my_const, other_binop = other_binop, my_const

        if (isinstance(my_const.expr, irast.Constant)
                and isinstance(other_binop.expr, irast.BinOp)
                and other_binop.expr.op == op):

            other_const = other_binop.expr.left
            other_binop_node = other_binop.expr.right
            if isinstance(other_binop_node.expr, irast.Constant):
                other_binop_node, other_const = \
                    other_const, other_binop_node

            if isinstance(other_const.expr, irast.Constant):
                new_const = try_fold_arithmetic_binop(op,
                                                      other_const,
                                                      my_const,
                                                      ctx=ctx)

                if new_const is not None:
                    folded_binop = irast.BinOp(left=new_const,
                                               right=other_binop_node,
                                               op=op)
                    folded = setgen.ensure_set(folded_binop, ctx=ctx)

    return folded
Exemplo n.º 14
0
    def _command_for_ast_node(cls, astnode, schema, context):
        classname = cls._classname_from_ast(astnode, context, schema)

        if isinstance(astnode, qlast.CreateView):
            expr = cls._get_view_expr(astnode)
            ir = cls._compile_view_expr(expr, classname, schema, context)
            scls = irutils.infer_type(ir, schema)
        else:
            scls = schema.get(classname)

        if isinstance(scls, s_scalars.ScalarType):
            mapping = cls._scalar_cmd_map
        else:
            mapping = cls._objtype_cmd_map

        return mapping[type(astnode)]
Exemplo n.º 15
0
def compile_type_check_op(expr: qlast.IsOp, *,
                          ctx: context.ContextLevel) -> irast.TypeCheckOp:
    # <Expr> IS <TypeExpr>
    left = dispatch.compile(expr.left, ctx=ctx)
    ltype = irutils.infer_type(left, ctx.schema)
    left = setgen.ptr_step_set(left,
                               source=ltype,
                               ptr_name=('std', '__type__'),
                               direction=s_pointers.PointerDirection.Outbound,
                               source_context=expr.context,
                               ctx=ctx)

    pathctx.register_set_in_scope(left, ctx=ctx)

    right = typegen.ql_typeref_to_ir_typeref(expr.right, ctx=ctx)
    return irast.TypeCheckOp(left=left, right=right, op=expr.op)
Exemplo n.º 16
0
def process_func_args(
        expr: qlast.FunctionCall, funcname: sn.Name, *,
        ctx: context.ContextLevel) \
        -> typing.Tuple[
            typing.List[irast.Base],            # args
            typing.Dict[str, irast.Base],       # kwargs
            typing.List[s_types.Type]]:    # arg_types
    args = []
    kwargs = {}
    arg_types = []

    for ai, a in enumerate(expr.args):
        arg_ql = a.arg

        if a.sort or a.filter:
            arg_ql = astutils.ensure_qlstmt(arg_ql)
            if a.filter:
                arg_ql.where = astutils.extend_qlbinop(arg_ql.where, a.filter)

            if a.sort:
                arg_ql.orderby = a.sort + arg_ql.orderby

        with ctx.newscope(fenced=True) as fencectx:
            # 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.
            arg = setgen.scoped_set(
                dispatch.compile(arg_ql, ctx=fencectx),
                ctx=fencectx)

        if a.name:
            kwargs[a.name] = arg
            aname = a.name
        else:
            args.append(arg)
            aname = ai

        arg_type = irutils.infer_type(arg, ctx.schema)
        if arg_type is None:
            raise errors.EdgeQLError(
                f'could not resolve the type of argument '
                f'${aname} of function {funcname}',
                context=a.context)
        arg_types.append(arg_type)

    return args, kwargs, arg_types
Exemplo n.º 17
0
    def _handle_view_op(cls, cmd, astnode, context, schema):
        from edb.lang.ir import utils as irutils

        view_expr = cls._maybe_get_view_expr(astnode)
        if view_expr is not None:
            ir = cls._compile_view_expr(view_expr, cmd.classname, schema,
                                        context)
            rt = irutils.infer_type(ir, schema)

            if rt.is_view():
                # The expression itself declares a view, use it.
                rt.name = cmd.classname

            view_schema = cls._view_schema_from_ir(cmd.classname, ir, schema)
            if isinstance(astnode, qlast.AlterObjectType):
                prev = schema.get(cmd.classname)
                prev_ir = cls._compile_view_expr(prev.expr, cmd.classname,
                                                 schema, context)
                prev_view_schema = cls._view_schema_from_ir(
                    cmd.classname, prev_ir, schema)
            else:
                prev_view_schema = cls._view_schema_from_ir(
                    cmd.classname, None, schema)

            derived_delta = sd.delta_schemas(view_schema,
                                             prev_view_schema,
                                             include_derived=True)

            if rt.is_view():
                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
Exemplo n.º 18
0
def compile_FunctionCall(
        expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Base:
    with ctx.new() as fctx:
        if isinstance(expr.func, str):
            funcname = expr.func
        else:
            funcname = sn.Name(expr.func[1], expr.func[0])

        funcs = fctx.schema.get_functions(
            funcname, module_aliases=fctx.modaliases)

        if funcs is None:
            raise errors.EdgeQLError(
                f'could not resolve function name {funcname}',
                context=expr.context)

        fctx.in_func_call = True
        args, kwargs, arg_types = process_func_args(expr, funcname, ctx=fctx)

        fatal_array_check = len(funcs) == 1
        for funcobj in funcs:
            if check_function(expr, funcname, funcobj, arg_types,
                              fatal_array_check=fatal_array_check):
                break
        else:
            raise errors.EdgeQLError(
                f'could not find a function variant {funcname}',
                context=expr.context)

        fixup_param_scope(funcobj, args, kwargs, ctx=fctx)

        node = irast.FunctionCall(func=funcobj, args=args, kwargs=kwargs)

        if funcobj.initial_value is not None:
            rtype = irutils.infer_type(node, fctx.schema)
            iv_ql = qlast.TypeCast(
                expr=qlparser.parse_fragment(funcobj.initial_value),
                type=typegen.type_to_ql_typeref(rtype)
            )
            node.initial_value = dispatch.compile(iv_ql, ctx=fctx)

    ir_set = setgen.ensure_set(node, ctx=ctx)
    return ir_set
Exemplo n.º 19
0
def compile_type_check_op(
        expr: qlast.BinOp, *, ctx: context.ContextLevel) -> irast.TypeCheckOp:
    # <Expr> IS <Type>
    left = dispatch.compile(expr.left, ctx=ctx)
    with ctx.new() as subctx:
        subctx.path_as_type = True
        right = dispatch.compile(expr.right, ctx=subctx)

    ltype = irutils.infer_type(left, ctx.schema)
    left = setgen.ptr_step_set(
        left, source=ltype, ptr_name=('std', '__type__'),
        direction=s_pointers.PointerDirection.Outbound,
        source_context=expr.context, ctx=ctx)

    pathctx.register_set_in_scope(left, ctx=ctx)

    right = typegen.process_type_ref_expr(right)

    return irast.TypeCheckOp(left=left, right=right, op=expr.op)
Exemplo n.º 20
0
def new_expression_set(ir_expr,
                       path_id=None,
                       alias=None,
                       typehint: typing.Optional[irast.TypeRef] = None,
                       *,
                       ctx: context.ContextLevel) -> irast.Set:
    if isinstance(ir_expr, irast.EmptySet) and typehint is not None:
        ir_expr = irast.TypeCast(expr=ir_expr, type=typehint)

    result_type = irutils.infer_type(ir_expr, ctx.schema)

    if path_id is None:
        path_id = getattr(ir_expr, 'path_id', None)

        if not path_id:
            if alias is None:
                raise ValueError('either path_id or alias are required')
            path_id = get_expression_path_id(result_type, alias, ctx=ctx)

    return new_set(path_id=path_id, scls=result_type, expr=ir_expr, ctx=ctx)
Exemplo n.º 21
0
def compile_TypeFilter(expr: qlast.Base, *,
                       ctx: context.ContextLevel) -> irast.Base:
    # Expr[IS Type] expressions.
    with ctx.new() as scopectx:
        arg = setgen.ensure_set(dispatch.compile(expr.expr, ctx=scopectx),
                                ctx=scopectx)

    arg_type = irutils.infer_type(arg, ctx.schema)
    if not isinstance(arg_type, s_objtypes.ObjectType):
        raise errors.EdgeQLError(
            f'invalid type filter operand: {arg_type.name} '
            f'is not an object type',
            context=expr.expr.context)

    typ = schemactx.get_schema_type(expr.type.maintype, ctx=ctx)
    if not isinstance(typ, s_objtypes.ObjectType):
        raise errors.EdgeQLError(
            f'invalid type filter operand: {typ.name} is not an object type',
            context=expr.type.context)

    return setgen.class_indirection_set(arg, typ, optional=False, ctx=ctx)
Exemplo n.º 22
0
    def _normalize_ptr_default(self, expr, source, ptr, ptrdecl):
        module_aliases = {None: source.name.module}

        ir, _, expr_text = edgeql.utils.normalize_tree(
            expr,
            self._schema,
            modaliases=module_aliases,
            anchors={qlast.Source: source})

        self_set = ast.find_children(
            ir,
            lambda n: getattr(n, 'anchor', None) == qlast.Source,
            terminate_early=True)

        try:
            expr_type = ir_utils.infer_type(ir, self._schema)
        except edgeql.EdgeQLError as e:
            raise s_err.SchemaError(
                'could not determine the result type of the default '
                'expression on {!s}.{!s}'.format(source.name, ptr.shortname),
                context=expr.context) from e

        ptr.default = expr_text
        ptr.normalize_defaults()

        if ptr.is_pure_computable():
            # Pure computable without explicit target.
            # Fixup pointer target and target property.
            ptr.target = expr_type

            if isinstance(ptr, s_links.Link):
                if not isinstance(expr_type, s_objtypes.ObjectType):
                    raise s_err.SchemaDefinitionError(
                        f'invalid link target, expected object type, got '
                        f'{expr_type.__class__.__name__}',
                        context=ptrdecl.expr.context)
            else:
                if not isinstance(expr_type,
                                  (s_scalars.ScalarType, s_types.Collection)):
                    raise s_err.SchemaDefinitionError(
                        f'invalid property target, expected primitive type, '
                        f'got {expr_type.__class__.__name__}',
                        context=ptrdecl.expr.context)

            if isinstance(ptr, s_links.Link):
                pname = s_name.Name('std::target')
                tgt_prop = ptr.pointers[pname]
                tgt_prop.target = expr_type

            cardinality = self._get_literal_attribute(ptrdecl, 'cardinality')
            if cardinality is not None:
                raise s_err.SchemaError(
                    'computable links must not define explicit cardinality',
                    context=expr.context)

            singletons = set()
            if self_set is not None:
                singletons.add(self_set.path_id)

            cardinality = \
                ir_inference.infer_cardinality(ir, singletons, self._schema)

            if cardinality == qlast.Cardinality.MANY:
                ptr.cardinality = s_pointers.PointerCardinality.ManyToMany
            else:
                ptr.cardinality = s_pointers.PointerCardinality.ManyToOne

        if (not isinstance(expr_type, s_types.Type) or
            (ptr.target is not None and not expr_type.issubclass(ptr.target))):
            raise s_err.SchemaError(
                'default value query must yield a single result of '
                'type {!r}'.format(ptr.target.name),
                context=expr.context)

        if not isinstance(ptr.target, s_scalars.ScalarType):
            many_mapping = (s_pointers.PointerCardinality.ManyToOne,
                            s_pointers.PointerCardinality.ManyToMany)
            if ptr.cardinality not in many_mapping:
                raise s_err.SchemaError(
                    'type links with query defaults '
                    'must have either a "*1" or "**" cardinality',
                    context=expr.context)
Exemplo n.º 23
0
def _infer_type(expr: irast.Base, *,
                ctx: context.CompilerContextLevel) -> s_obj.Object:
    return irutils.infer_type(expr, schema=ctx.env.schema)
Exemplo n.º 24
0
    def _cmd_tree_from_ast(cls, astnode, context, schema):
        from edb.lang.edgeql import utils as ql_utils
        from edb.lang.ir import ast as irast
        from edb.lang.ir import inference as ir_inference
        from edb.lang.ir import utils as ir_utils
        from . import objtypes as s_objtypes

        cmd = super()._cmd_tree_from_ast(astnode, context, schema)

        if isinstance(astnode, qlast.CreateConcreteLink):
            cmd.add(
                sd.AlterObjectProperty(property='required',
                                       new_value=astnode.is_required))

            # "source" attribute is set automatically as a refdict back-attr
            parent_ctx = context.get(LinkSourceCommandContext)
            source_name = parent_ctx.op.classname
            target_type = None

            if len(astnode.targets) > 1:
                cmd.add(
                    sd.AlterObjectProperty(
                        property='spectargets',
                        new_value=so.ObjectList([
                            utils.ast_to_typeref(t,
                                                 modaliases=context.modaliases,
                                                 schema=schema)
                            for t in astnode.targets
                        ])))

                target_name = sources.Source.gen_virt_parent_name(
                    (sn.Name(module=t.maintype.module, name=t.maintype.name)
                     for t in astnode.targets),
                    module=source_name.module)

                target = so.ObjectRef(classname=target_name)

                create_virt_parent = s_objtypes.CreateObjectType(
                    classname=target_name, metaclass=s_objtypes.ObjectType)

                create_virt_parent.update(
                    (sd.AlterObjectProperty(
                        property='bases',
                        new_value=so.ObjectList([
                            so.ObjectRef(
                                classname=sn.Name(module='std', name='Object'))
                        ])),
                     sd.AlterObjectProperty(property='name',
                                            new_value=target_name),
                     sd.AlterObjectProperty(property='is_virtual',
                                            new_value=True)))

                alter_db_ctx = context.get(s_db.DatabaseCommandContext)

                for cc in alter_db_ctx.op.get_subcommands(
                        type=s_objtypes.CreateObjectType):
                    if cc.classname == create_virt_parent.classname:
                        break
                else:
                    alter_db_ctx.op.add(create_virt_parent)
            else:
                target_expr = astnode.targets[0]
                if isinstance(target_expr, qlast.TypeName):
                    target = utils.ast_to_typeref(
                        target_expr,
                        modaliases=context.modaliases,
                        schema=schema)
                else:
                    # computable
                    source = schema.get(source_name, default=None)
                    if source is None:
                        raise s_err.SchemaDefinitionError(
                            f'cannot define link computables in CREATE TYPE',
                            hint='Perform a CREATE TYPE without the link '
                            'followed by ALTER TYPE defining the '
                            'computable',
                            context=target_expr.context)

                    ir, _, target_expr = ql_utils.normalize_tree(
                        target_expr, schema, anchors={qlast.Source: source})

                    try:
                        target_type = ir_utils.infer_type(ir, schema)
                    except edgeql.EdgeQLError as e:
                        raise s_err.SchemaDefinitionError(
                            'could not determine the result type of '
                            'computable expression',
                            context=target_expr.context) from e

                    target = utils.reduce_to_typeref(target_type)

                    cmd.add(
                        sd.AlterObjectProperty(property='default',
                                               new_value=target_expr))

                    cmd.add(
                        sd.AlterObjectProperty(property='computable',
                                               new_value=True))

                    singletons = {irast.PathId(source)}

                    cardinality = ir_inference.infer_cardinality(
                        ir, singletons, schema)

                    if cardinality == qlast.Cardinality.ONE:
                        link_card = pointers.PointerCardinality.ManyToOne
                    else:
                        link_card = pointers.PointerCardinality.ManyToMany

                    cmd.add(
                        sd.AlterObjectProperty(property='cardinality',
                                               new_value=link_card))

            if (isinstance(target, so.ObjectRef)
                    and target.classname == source_name):
                # Special case for loop links.  Since the target
                # is the same as the source, we know it's a proper
                # type.
                pass
            else:
                if target_type is None:
                    target_type = utils.resolve_typeref(target, schema=schema)

                if not isinstance(target_type, s_objtypes.ObjectType):
                    raise s_err.SchemaDefinitionError(
                        f'invalid link target, expected object type, got '
                        f'{target_type.__class__.__name__}',
                        context=astnode.targets[0].context)

            cmd.add(sd.AlterObjectProperty(property='target',
                                           new_value=target))

            base_prop_name = sn.Name('std::source')
            s_name = lproperties.Property.get_specialized_name(
                base_prop_name, cmd.classname)
            src_prop_name = sn.Name(name=s_name, module=cmd.classname.module)

            src_prop = lproperties.CreateProperty(
                classname=src_prop_name, metaclass=lproperties.Property)
            src_prop.update((
                sd.AlterObjectProperty(property='name',
                                       new_value=src_prop_name),
                sd.AlterObjectProperty(
                    property='bases',
                    new_value=[so.ObjectRef(classname=base_prop_name)]),
                sd.AlterObjectProperty(
                    property='source',
                    new_value=so.ObjectRef(classname=cmd.classname)),
                sd.AlterObjectProperty(
                    property='target',
                    new_value=so.ObjectRef(classname=source_name)),
                sd.AlterObjectProperty(property='required', new_value=True),
                sd.AlterObjectProperty(property='readonly', new_value=True),
            ))

            cmd.add(src_prop)

            base_prop_name = sn.Name('std::target')
            s_name = lproperties.Property.get_specialized_name(
                base_prop_name, cmd.classname)
            tgt_prop_name = sn.Name(name=s_name, module=cmd.classname.module)

            tgt_prop = lproperties.CreateProperty(
                classname=tgt_prop_name, metaclass=lproperties.Property)
            tgt_prop.update((
                sd.AlterObjectProperty(property='name',
                                       new_value=tgt_prop_name),
                sd.AlterObjectProperty(
                    property='bases',
                    new_value=[so.ObjectRef(classname=base_prop_name)]),
                sd.AlterObjectProperty(
                    property='source',
                    new_value=so.ObjectRef(classname=cmd.classname)),
                sd.AlterObjectProperty(property='target', new_value=target),
                sd.AlterObjectProperty(property='required', new_value=False),
                sd.AlterObjectProperty(property='readonly', new_value=True),
            ))

            cmd.add(tgt_prop)

            cls._parse_default(cmd)

        return cmd
Exemplo n.º 25
0
def _normalize_view_ptr_expr(
        shape_el: qlast.ShapeElement,
        view_scls: s_nodes.Node,
        *,
        path_id: irast.PathId,
        is_insert: bool = False,
        is_update: bool = False,
        view_rptr: typing.Optional[context.ViewRPtr] = None,
        ctx: context.CompilerContext) -> s_pointers.Pointer:
    steps = shape_el.expr.steps
    is_linkprop = False
    is_mutation = is_insert or is_update
    # Pointers may be qualified by the explicit source
    # class, which is equivalent to Expr[IS Type].
    is_polymorphic = len(steps) == 2
    scls = view_scls.peel_view()
    ptrsource = scls
    qlexpr = None

    if is_polymorphic:
        source = qlast.TypeFilter(expr=qlast.Path(steps=[qlast.Source()]),
                                  type=qlast.TypeName(maintype=steps[0]))
        lexpr = steps[1]
        ptrsource = schemactx.get_schema_type(steps[0], ctx=ctx)
    elif len(steps) == 1:
        lexpr = steps[0]
        is_linkprop = lexpr.type == 'property'
        if is_linkprop:
            if view_rptr is None:
                raise errors.EdgeQLError(
                    'invalid reference to link property '
                    'in top level shape',
                    context=lexpr.context)
            if view_rptr.ptrcls is None:
                derive_ptrcls(view_rptr, target_scls=view_scls, ctx=ctx)
            ptrsource = scls = view_rptr.ptrcls
        source = qlast.Source()
    else:
        raise RuntimeError(
            f'unexpected path length in view shape: {len(steps)}')

    ptrname = (lexpr.ptr.module, lexpr.ptr.name)
    ptrcls_is_derived = False

    compexpr = shape_el.compexpr
    if compexpr is None and is_insert and shape_el.elements:
        # Nested insert short form:
        #     INSERT Foo { bar: Spam { name := 'name' }}
        # Expand to:
        #     INSERT Foo { bar := (INSERT Spam { name := 'name' }) }
        if lexpr.target is not None:
            ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx)
        else:
            ptr_target = None

        base_ptrcls = ptrcls = setgen.resolve_ptr(
            ptrsource,
            ptrname,
            s_pointers.PointerDirection.Outbound,
            target=ptr_target,
            ctx=ctx)

        compexpr = qlast.InsertQuery(subject=qlast.Path(steps=[
            qlast.ObjectRef(name=ptrcls.target.name.name,
                            module=ptrcls.target.name.module)
        ]),
                                     shape=shape_el.elements)

    if compexpr is None:
        if lexpr.target is not None:
            ptr_target = schemactx.get_schema_type(lexpr.target, ctx=ctx)
        else:
            ptr_target = None

        base_ptrcls = ptrcls = setgen.resolve_ptr(
            ptrsource,
            ptrname,
            s_pointers.PointerDirection.Outbound,
            target=ptr_target,
            ctx=ctx)

        base_ptr_is_computable = ptrcls in ctx.source_map

        if ptr_target is not None and ptr_target != base_ptrcls.target:
            # This happens when a union type target is narrowed by an
            # [IS Type] construct.  Since the derived pointer will have
            # the correct target, we don't need to do anything, but
            # remove the [IS] qualifier to prevent recursion.
            lexpr.target = None
        else:
            ptr_target = ptrcls.target

        if ptrcls in ctx.pending_cardinality:
            # We do not know the parent's pointer cardinality yet.
            ptr_cardinality = None
        else:
            ptr_cardinality = ptrcls.cardinality

        if shape_el.elements:
            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 = path_id.extend(ptrcls, target=ptrcls.target)
            ctx.path_scope.attach_path(sub_path_id)

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

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

            ptrcls = sub_view_rptr.derived_ptrcls
            if ptrcls is None:
                ptrcls_is_derived = False
                ptrcls = sub_view_rptr.ptrcls
            else:
                ptrcls_is_derived = True

        if (shape_el.where or shape_el.orderby or shape_el.offset
                or shape_el.limit or base_ptr_is_computable or is_polymorphic):

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

            qlexpr = astutils.ensure_qlstmt(qlexpr)
            qlexpr.where = shape_el.where
            qlexpr.orderby = shape_el.orderby
            qlexpr.offset = shape_el.offset
            qlexpr.limit = shape_el.limit
    else:
        try:
            base_ptrcls = ptrcls = setgen.resolve_ptr(
                ptrsource,
                ptrname,
                s_pointers.PointerDirection.Outbound,
                ctx=ctx)

            ptr_name = ptrcls.shortname
        except errors.EdgeQLReferenceError:
            if is_mutation:
                raise

            base_ptrcls = ptrcls = None

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

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

        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.path_scope.unnest_fence = True

            if is_mutation:
                shape_expr_ctx.expr_exposed = True

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

            irexpr.context = compexpr.context

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

            derived_ptrcls = shape_expr_ctx.view_rptr.derived_ptrcls
            if derived_ptrcls is not None:
                ptrcls_is_derived = True
                ptrcls = derived_ptrcls

        ptr_cardinality = None

        ptr_target = irutils.infer_type(irexpr, ctx.schema)
        if ptr_target is None:
            msg = 'cannot determine expression result type'
            raise errors.EdgeQLError(msg, context=shape_el.context)

        if is_mutation and not ptr_target.assignment_castable_to(
                base_ptrcls.target, schema=ctx.schema):
            # Validate that the insert/update expression is
            # of the correct class.
            lname = f'({ptrsource.name}).{ptrcls.shortname.name}'
            expected = [repr(str(base_ptrcls.target.name))]
            raise edgedb_error.InvalidPointerTargetError(
                f'invalid target for link {str(lname)!r}: '
                f'{str(ptr_target.name)!r} (expecting '
                f'{" or ".join(expected)})')

    if qlexpr is not None or ptr_target is not ptrcls.target:
        if not ptrcls_is_derived:
            if is_linkprop:
                rptrcls = view_rptr.derived_ptrcls
                if rptrcls is None:
                    rptrcls = derive_ptrcls(view_rptr,
                                            target_scls=view_scls,
                                            ctx=ctx)

                src_scls = rptrcls
            else:
                src_scls = view_scls

            ptrcls = schemactx.derive_view(ptrcls,
                                           src_scls,
                                           ptr_target,
                                           is_insert=is_insert,
                                           is_update=is_update,
                                           derived_name_quals=[view_scls.name],
                                           ctx=ctx)

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

    if not is_mutation:
        if ptr_cardinality is None:
            if compexpr is not None:
                ctx.pending_cardinality.add(ptrcls)
            elif ptrcls is not base_ptrcls:
                ctx.pointer_derivation_map[base_ptrcls].append(ptrcls)

            ptrcls.cardinality = None
        else:
            ptrcls.cardinality = ptr_cardinality

    if ptrcls.is_protected_pointer() and qlexpr is not None:
        msg = f'cannot assign to {ptrcls.shortname.name}'
        raise errors.EdgeQLError(msg, context=shape_el.context)

    return ptrcls