Ejemplo n.º 1
0
def process_linkprop_update(ir_stmt: irast.MutatingStmt, ir_expr: irast.Set,
                            wrapper: pgast.Query,
                            dml_cte: pgast.CommonTableExpr, *,
                            ctx: context.CompilerContextLevel) -> None:
    """Perform link property updates to a link relation.

    :param ir_stmt:
        IR of the statement.
    :param ir_expr:
        IR of the UPDATE body element.
    :param wrapper:
        Top-level SQL query.
    :param dml_cte:
        CTE representing the SQL UPDATE to the main relation of the Object.
    """
    toplevel = ctx.toplevel_stmt

    rptr = ir_expr.rptr
    ptrref = rptr.ptrref

    if ptrref.material_ptr:
        ptrref = ptrref.material_ptr

    target_tab = relctx.range_for_ptrref(ptrref,
                                         include_overlays=False,
                                         ctx=ctx)

    dml_cte_rvar = pgast.RelRangeVar(
        relation=dml_cte,
        alias=pgast.Alias(aliasname=ctx.env.aliases.get('m')))

    cond = astutils.new_binop(
        pathctx.get_rvar_path_identity_var(dml_cte_rvar,
                                           ir_stmt.subject.path_id,
                                           env=ctx.env),
        astutils.get_column(target_tab, 'source', nullable=False),
        op='=',
    )

    targets = []
    for prop_el, shape_op in ir_expr.shape:
        assert shape_op is qlast.ShapeOp.ASSIGN
        ptrname = prop_el.rptr.ptrref.shortname
        with ctx.new() as input_rel_ctx:
            input_rel_ctx.expr_exposed = False
            input_rel = dispatch.compile(prop_el.expr, ctx=input_rel_ctx)
            targets.append(pgast.UpdateTarget(name=ptrname.name,
                                              val=input_rel))

    updstmt = pgast.UpdateStmt(relation=target_tab,
                               where_clause=cond,
                               targets=targets,
                               from_clause=[dml_cte_rvar])

    updcte = pgast.CommonTableExpr(query=updstmt,
                                   name=ctx.env.aliases.get(
                                       ptrref.shortname.name))

    toplevel.ctes.append(updcte)
Ejemplo n.º 2
0
def scan_check_ctes(
    stmt: pgast.Query,
    check_ctes: List[pgast.CommonTableExpr],
    *,
    ctx: context.CompilerContextLevel,
) -> None:
    if not check_ctes:
        return

    # Scan all of the check CTEs to enforce constraints that are
    # checked as explicit queries and not Postgres constraints or
    # triggers.

    # To make sure that Postgres can't optimize the checks away, we
    # reference them in the where clause of an UPDATE to a dummy
    # table.

    # Add a big random number, so that different queries should try to
    # access different "rows" of the table, in case that matters.
    base_int = random.randint(0, (1 << 60) - 1)
    val: pgast.BaseExpr = pgast.NumericConstant(val=str(base_int))

    for check_cte in check_ctes:
        # We want the CTE to be MATERIALIZED, because otherwise
        # Postgres might not fully evaluate all its columns when
        # scanning it.
        check_cte.materialized = True
        check = pgast.SelectStmt(
            target_list=[
                pgast.ResTarget(val=pgast.FuncCall(name=('count', ),
                                                   args=[pgast.Star()]), )
            ],
            from_clause=[
                relctx.rvar_for_rel(check_cte, ctx=ctx),
            ],
        )
        val = pgast.Expr(kind=pgast.ExprKind.OP,
                         name='+',
                         lexpr=val,
                         rexpr=check)

    update_query = pgast.UpdateStmt(
        targets=[
            pgast.UpdateTarget(name='flag',
                               val=pgast.BooleanConstant(val='true'))
        ],
        relation=pgast.RelRangeVar(
            relation=pgast.Relation(schemaname='edgedb', name='_dml_dummy')),
        where_clause=pgast.Expr(
            kind=pgast.ExprKind.OP,
            name='=',
            lexpr=pgast.ColumnRef(name=['id']),
            rexpr=val,
        ))
    stmt.append_cte(
        pgast.CommonTableExpr(query=update_query,
                              name=ctx.env.aliases.get(hint='check_scan')))
Ejemplo n.º 3
0
def process_update_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query,
                        update_cte: pgast.CommonTableExpr,
                        range_cte: pgast.CommonTableExpr, *,
                        ctx: context.CompilerContextLevel):
    """Generate SQL DML CTEs from an UpdateStmt IR.

    :param ir_stmt:
        IR of the statement.
    :param wrapper:
        Top-level SQL query.
    :param update_cte:
        CTE representing the SQL UPDATE to the main relation of the Object.
    :param range_cte:
        CTE representing the range affected by the statement.
    """
    update_stmt = update_cte.query
    assert isinstance(update_stmt, pgast.UpdateStmt)

    external_updates = []

    toplevel = ctx.toplevel_stmt
    toplevel.ctes.append(range_cte)
    toplevel.ctes.append(update_cte)

    with ctx.newscope() as subctx:
        # It is necessary to process the expressions in
        # the UpdateStmt shape body in the context of the
        # UPDATE statement so that references to the current
        # values of the updated object are resolved correctly.
        subctx.path_scope[ir_stmt.subject.path_id] = update_stmt
        subctx.rel = update_stmt
        subctx.expr_exposed = False

        for shape_el in ir_stmt.subject.shape:
            ptrref = shape_el.rptr.ptrref
            updvalue = shape_el.expr
            ptr_info = pg_types.get_ptrref_storage_info(ptrref,
                                                        resolve_type=True,
                                                        link_bias=False)

            if ptr_info.table_type == 'ObjectType' and updvalue is not None:
                with subctx.newscope() as scopectx:
                    # First, process all internal link updates
                    updtarget = pgast.UpdateTarget(
                        name=ptr_info.column_name,
                        val=pgast.TypeCast(arg=dispatch.compile(updvalue,
                                                                ctx=scopectx),
                                           type_name=pgast.TypeName(
                                               name=ptr_info.column_type)))

                    update_stmt.targets.append(updtarget)

            props_only = is_props_only_update(shape_el, ctx=subctx)

            ptr_info = pg_types.get_ptrref_storage_info(ptrref,
                                                        resolve_type=False,
                                                        link_bias=True)

            if ptr_info and ptr_info.table_type == 'link':
                external_updates.append((shape_el, props_only))

    if not update_stmt.targets:
        # No updates directly to the set target table,
        # so convert the UPDATE statement into a SELECT.
        from_clause: typing.List[pgast.BaseRangeVar] = [update_stmt.relation]
        from_clause.extend(update_stmt.from_clause)
        update_cte.query = pgast.SelectStmt(
            ctes=update_stmt.ctes,
            target_list=update_stmt.returning_list,
            from_clause=from_clause,
            where_clause=update_stmt.where_clause,
            path_namespace=update_stmt.path_namespace,
            path_outputs=update_stmt.path_outputs,
            path_scope=update_stmt.path_scope,
            path_rvar_map=update_stmt.path_rvar_map.copy(),
            view_path_id_map=update_stmt.view_path_id_map.copy(),
            ptr_join_map=update_stmt.ptr_join_map.copy(),
        )

    # Process necessary updates to the link tables.
    for expr, props_only in external_updates:
        process_link_update(ir_stmt=ir_stmt,
                            ir_set=expr,
                            props_only=False,
                            wrapper=wrapper,
                            dml_cte=update_cte,
                            iterator_cte=None,
                            is_insert=False,
                            ctx=ctx)
Ejemplo n.º 4
0
def process_update_body(
    ir_stmt: irast.MutatingStmt,
    wrapper: pgast.Query,
    update_cte: pgast.CommonTableExpr,
    typeref: irast.TypeRef,
    *,
    ctx: context.CompilerContextLevel,
) -> None:
    """Generate SQL DML CTEs from an UpdateStmt IR.

    :param ir_stmt:
        IR of the statement.
    :param wrapper:
        Top-level SQL query.
    :param update_cte:
        CTE representing the SQL UPDATE to the main relation of the Object.
    :param typeref:
        The specific TypeRef of a set being updated.
    """
    update_stmt = update_cte.query
    assert isinstance(update_stmt, pgast.UpdateStmt)

    external_updates = []

    with ctx.newscope() as subctx:
        # It is necessary to process the expressions in
        # the UpdateStmt shape body in the context of the
        # UPDATE statement so that references to the current
        # values of the updated object are resolved correctly.
        subctx.parent_rel = update_stmt
        subctx.expr_exposed = False

        for shape_el, shape_op in ir_stmt.subject.shape:
            ptrref = shape_el.rptr.ptrref
            updvalue = shape_el.expr
            ptr_info = pg_types.get_ptrref_storage_info(ptrref,
                                                        resolve_type=True,
                                                        link_bias=False)

            if ptr_info.table_type == 'ObjectType' and updvalue is not None:
                with subctx.newscope() as scopectx:
                    val: pgast.BaseExpr

                    if irtyputils.is_tuple(shape_el.typeref):
                        # When target is a tuple type, make sure
                        # the expression is compiled into a subquery
                        # returning a single column that is explicitly
                        # cast into the appropriate composite type.
                        val = relgen.set_as_subquery(
                            shape_el,
                            as_value=True,
                            explicit_cast=ptr_info.column_type,
                            ctx=scopectx,
                        )
                    else:
                        if (isinstance(updvalue, irast.MutatingStmt)
                                and updvalue in ctx.dml_stmts):
                            with scopectx.substmt() as relctx:
                                dml_cte = ctx.dml_stmts[updvalue]
                                wrap_dml_cte(updvalue, dml_cte, ctx=relctx)
                                pathctx.get_path_identity_output(
                                    relctx.rel,
                                    updvalue.subject.path_id,
                                    env=relctx.env,
                                )
                                val = relctx.rel
                        else:
                            val = dispatch.compile(updvalue, ctx=scopectx)

                        val = pgast.TypeCast(arg=val,
                                             type_name=pgast.TypeName(
                                                 name=ptr_info.column_type))

                    if shape_op is qlast.ShapeOp.SUBTRACT:
                        val = pgast.FuncCall(
                            name=('nullif', ),
                            args=[
                                pgast.ColumnRef(name=(ptr_info.column_name, )),
                                val,
                            ],
                        )

                    updtarget = pgast.UpdateTarget(
                        name=ptr_info.column_name,
                        val=val,
                    )

                    update_stmt.targets.append(updtarget)

            props_only = is_props_only_update(shape_el, ctx=subctx)

            ptr_info = pg_types.get_ptrref_storage_info(ptrref,
                                                        resolve_type=False,
                                                        link_bias=True)

            if ptr_info and ptr_info.table_type == 'link':
                external_updates.append((shape_el, shape_op, props_only))

    if not update_stmt.targets:
        # No updates directly to the set target table,
        # so convert the UPDATE statement into a SELECT.
        from_clause: List[pgast.BaseRangeVar] = [update_stmt.relation]
        from_clause.extend(update_stmt.from_clause)
        update_cte.query = pgast.SelectStmt(
            ctes=update_stmt.ctes,
            target_list=update_stmt.returning_list,
            from_clause=from_clause,
            where_clause=update_stmt.where_clause,
            path_namespace=update_stmt.path_namespace,
            path_outputs=update_stmt.path_outputs,
            path_scope=update_stmt.path_scope,
            path_rvar_map=update_stmt.path_rvar_map.copy(),
            view_path_id_map=update_stmt.view_path_id_map.copy(),
            ptr_join_map=update_stmt.ptr_join_map.copy(),
        )

    toplevel = ctx.toplevel_stmt
    toplevel.ctes.append(update_cte)

    # Process necessary updates to the link tables.
    for expr, shape_op, _ in external_updates:
        process_link_update(
            ir_stmt=ir_stmt,
            ir_set=expr,
            props_only=False,
            wrapper=wrapper,
            dml_cte=update_cte,
            iterator_cte=None,
            is_insert=False,
            shape_op=shape_op,
            source_typeref=typeref,
            ctx=ctx,
        )