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)
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')))
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)
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