Пример #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)
Пример #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')))
Пример #3
0
def compile_UpdateStmt(
        stmt: irast.UpdateStmt, *,
        ctx: context.CompilerContextLevel) -> pgast.Query:

    parent_ctx = ctx
    with parent_ctx.substmt() as ctx:
        # Common DML bootstrap.
        wrapper, update_cte, update_rvar, range_cte = dml.init_dml_stmt(
            stmt, pgast.UpdateStmt(), parent_ctx=parent_ctx, ctx=ctx)

        # Process UPDATE body.
        assert range_cte is not None
        dml.process_update_body(stmt, wrapper, update_cte, range_cte, ctx=ctx)

        return dml.fini_dml_stmt(stmt, wrapper, update_cte, update_rvar,
                                 parent_ctx=parent_ctx, ctx=ctx)
Пример #4
0
def gen_dml_cte(
    ir_stmt: irast.MutatingStmt,
    *,
    range_rvar: Optional[pgast.RelRangeVar],
    typeref: irast.TypeRef,
    ctx: context.CompilerContextLevel,
) -> Tuple[pgast.CommonTableExpr, pgast.PathRangeVar]:

    target_ir_set = ir_stmt.subject
    target_path_id = target_ir_set.path_id

    dml_stmt: pgast.Query
    if isinstance(ir_stmt, irast.InsertStmt):
        dml_stmt = pgast.InsertStmt()
    elif isinstance(ir_stmt, irast.UpdateStmt):
        dml_stmt = pgast.UpdateStmt()
    elif isinstance(ir_stmt, irast.DeleteStmt):
        dml_stmt = pgast.DeleteStmt()
    else:
        raise AssertionError(f'unexpected DML IR: {ir_stmt!r}')

    dml_stmt.relation = relctx.range_for_typeref(
        typeref,
        target_path_id,
        for_mutation=True,
        common_parent=True,
        ctx=ctx,
    )
    pathctx.put_path_value_rvar(dml_stmt,
                                target_path_id,
                                dml_stmt.relation,
                                env=ctx.env)
    pathctx.put_path_source_rvar(dml_stmt,
                                 target_path_id,
                                 dml_stmt.relation,
                                 env=ctx.env)
    pathctx.put_path_bond(dml_stmt, target_path_id)

    dml_cte = pgast.CommonTableExpr(query=dml_stmt,
                                    name=ctx.env.aliases.get(hint='m'))

    if range_rvar is not None:
        relctx.pull_path_namespace(target=dml_stmt, source=range_rvar, ctx=ctx)

        # Auxiliary relations are always joined via the WHERE
        # clause due to the structure of the UPDATE/DELETE SQL statements.
        dml_stmt.where_clause = astutils.new_binop(
            lexpr=pgast.ColumnRef(
                name=[dml_stmt.relation.alias.aliasname, 'id']),
            op='=',
            rexpr=pathctx.get_rvar_path_identity_var(range_rvar,
                                                     target_ir_set.path_id,
                                                     env=ctx.env))

        # UPDATE has "FROM", while DELETE has "USING".
        if isinstance(dml_stmt, pgast.UpdateStmt):
            dml_stmt.from_clause.append(range_rvar)
        elif isinstance(dml_stmt, pgast.DeleteStmt):
            dml_stmt.using_clause.append(range_rvar)

    # Due to the fact that DML statements are structured
    # as a flat list of CTEs instead of nested range vars,
    # the top level path scope must be empty.  The necessary
    # range vars will be injected explicitly in all rels that
    # need them.
    ctx.path_scope.clear()

    pathctx.put_path_value_rvar(dml_stmt,
                                target_path_id,
                                dml_stmt.relation,
                                env=ctx.env)

    pathctx.put_path_source_rvar(dml_stmt,
                                 target_path_id,
                                 dml_stmt.relation,
                                 env=ctx.env)

    dml_rvar = relctx.rvar_for_rel(dml_cte, typeref=typeref, ctx=ctx)

    return dml_cte, dml_rvar