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