mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 00:03:57 -04:00 
			
		
		
		
	Fix UPDATE/DELETE WHERE CURRENT OF to support repeated update and update-
then-delete on the current cursor row. The basic fix is that nodeTidscan.c has to apply heap_get_latest_tid() to the current-scan-TID obtained from the cursor query; this ensures we get the latest row version to work with. However, since that only works if the query plan is a TID scan, we also have to hack the planner to make sure only that type of plan will be selected. (Formerly, the planner might decide to apply a seqscan if the table is very small. This change is probably a Good Thing anyway, since it's hard to see how a seqscan could really win.) That means the execQual.c code to support CurrentOfExpr as a regular expression type is dead code, so replace it with just an elog(). Also, add regression tests covering these cases. Note that the added tests expose the fact that re-fetching an updated row misbehaves if the cursor used FOR UPDATE. That's an independent bug that should be fixed later. Per report from Dharmendra Goyal.
This commit is contained in:
		
							parent
							
								
									9226ba817b
								
							
						
					
					
						commit
						c29a9c37bf
					
				| @ -8,7 +8,7 @@ | ||||
|  * | ||||
|  * | ||||
|  * IDENTIFICATION | ||||
|  *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.222 2007/09/06 17:31:58 tgl Exp $ | ||||
|  *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.223 2007/10/24 18:37:08 tgl Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
| @ -3694,45 +3694,17 @@ ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, | ||||
| /* ----------------------------------------------------------------
 | ||||
|  *		ExecEvalCurrentOfExpr | ||||
|  * | ||||
|  * Normally, the planner will convert CURRENT OF into a TidScan qualification, | ||||
|  * but we have plain execQual support in case it doesn't. | ||||
|  * The planner must convert CURRENT OF into a TidScan qualification. | ||||
|  * So, we have to be able to do ExecInitExpr on a CurrentOfExpr, | ||||
|  * but we shouldn't ever actually execute it. | ||||
|  * ---------------------------------------------------------------- | ||||
|  */ | ||||
| static Datum | ||||
| ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, | ||||
| 					  bool *isNull, ExprDoneCond *isDone) | ||||
| { | ||||
| 	CurrentOfExpr *cexpr = (CurrentOfExpr *) exprstate->expr; | ||||
| 	bool	result; | ||||
| 	bool	lisnull; | ||||
| 	Oid		tableoid; | ||||
| 	ItemPointer tuple_tid; | ||||
| 	ItemPointerData cursor_tid; | ||||
| 
 | ||||
| 	if (isDone) | ||||
| 		*isDone = ExprSingleResult; | ||||
| 	*isNull = false; | ||||
| 
 | ||||
| 	Assert(cexpr->cvarno != INNER); | ||||
| 	Assert(cexpr->cvarno != OUTER); | ||||
| 	Assert(!TupIsNull(econtext->ecxt_scantuple)); | ||||
| 	/* Use slot_getattr to catch any possible mistakes */ | ||||
| 	tableoid = DatumGetObjectId(slot_getattr(econtext->ecxt_scantuple, | ||||
| 											 TableOidAttributeNumber, | ||||
| 											 &lisnull)); | ||||
| 	Assert(!lisnull); | ||||
| 	tuple_tid = (ItemPointer) | ||||
| 		DatumGetPointer(slot_getattr(econtext->ecxt_scantuple, | ||||
| 									 SelfItemPointerAttributeNumber, | ||||
| 									 &lisnull)); | ||||
| 	Assert(!lisnull); | ||||
| 
 | ||||
| 	if (execCurrentOf(cexpr, econtext, tableoid, &cursor_tid)) | ||||
| 		result = ItemPointerEquals(&cursor_tid, tuple_tid); | ||||
| 	else | ||||
| 		result = false; | ||||
| 
 | ||||
| 	return BoolGetDatum(result); | ||||
| 	elog(ERROR, "CURRENT OF cannot be executed"); | ||||
| 	return 0;					/* keep compiler quiet */ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|  * | ||||
|  * | ||||
|  * IDENTIFICATION | ||||
|  *	  $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.55 2007/06/11 22:22:40 tgl Exp $ | ||||
|  *	  $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.56 2007/10/24 18:37:08 tgl Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
| @ -68,6 +68,7 @@ TidListCreate(TidScanState *tidstate) | ||||
| 	tidList = (ItemPointerData *) | ||||
| 		palloc(numAllocTids * sizeof(ItemPointerData)); | ||||
| 	numTids = 0; | ||||
| 	tidstate->tss_isCurrentOf = false; | ||||
| 
 | ||||
| 	foreach(l, evalList) | ||||
| 	{ | ||||
| @ -165,6 +166,7 @@ TidListCreate(TidScanState *tidstate) | ||||
| 								 numAllocTids * sizeof(ItemPointerData)); | ||||
| 				} | ||||
| 				tidList[numTids++] = cursor_tid; | ||||
| 				tidstate->tss_isCurrentOf = true; | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| @ -182,6 +184,9 @@ TidListCreate(TidScanState *tidstate) | ||||
| 		int			lastTid; | ||||
| 		int			i; | ||||
| 
 | ||||
| 		/* CurrentOfExpr could never appear OR'd with something else */ | ||||
| 		Assert(!tidstate->tss_isCurrentOf); | ||||
| 
 | ||||
| 		qsort((void *) tidList, numTids, sizeof(ItemPointerData), | ||||
| 			  itemptr_comparator); | ||||
| 		lastTid = 0; | ||||
| @ -269,7 +274,8 @@ TidNext(TidScanState *node) | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * XXX shouldn't we check here to make sure tuple matches TID list? In | ||||
| 		 * runtime-key case this is not certain, is it? | ||||
| 		 * runtime-key case this is not certain, is it?  However, in the | ||||
| 		 * WHERE CURRENT OF case it might not match anyway ... | ||||
| 		 */ | ||||
| 
 | ||||
| 		ExecStoreTuple(estate->es_evTuple[scanrelid - 1], | ||||
| @ -319,6 +325,15 @@ TidNext(TidScanState *node) | ||||
| 	while (node->tss_TidPtr >= 0 && node->tss_TidPtr < numTids) | ||||
| 	{ | ||||
| 		tuple->t_self = tidList[node->tss_TidPtr]; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * For WHERE CURRENT OF, the tuple retrieved from the cursor might | ||||
| 		 * since have been updated; if so, we should fetch the version that | ||||
| 		 * is current according to our snapshot. | ||||
| 		 */ | ||||
| 		if (node->tss_isCurrentOf) | ||||
| 			heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self); | ||||
| 
 | ||||
| 		if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL)) | ||||
| 		{ | ||||
| 			/*
 | ||||
|  | ||||
| @ -54,7 +54,7 @@ | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  * IDENTIFICATION | ||||
|  *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.186 2007/09/22 21:36:40 tgl Exp $ | ||||
|  *	  $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.187 2007/10/24 18:37:08 tgl Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
| @ -769,6 +769,7 @@ cost_tidscan(Path *path, PlannerInfo *root, | ||||
| { | ||||
| 	Cost		startup_cost = 0; | ||||
| 	Cost		run_cost = 0; | ||||
| 	bool		isCurrentOf = false; | ||||
| 	Cost		cpu_per_tuple; | ||||
| 	QualCost	tid_qual_cost; | ||||
| 	int			ntuples; | ||||
| @ -778,9 +779,6 @@ cost_tidscan(Path *path, PlannerInfo *root, | ||||
| 	Assert(baserel->relid > 0); | ||||
| 	Assert(baserel->rtekind == RTE_RELATION); | ||||
| 
 | ||||
| 	if (!enable_tidscan) | ||||
| 		startup_cost += disable_cost; | ||||
| 
 | ||||
| 	/* Count how many tuples we expect to retrieve */ | ||||
| 	ntuples = 0; | ||||
| 	foreach(l, tidquals) | ||||
| @ -793,6 +791,12 @@ cost_tidscan(Path *path, PlannerInfo *root, | ||||
| 
 | ||||
| 			ntuples += estimate_array_length(arraynode); | ||||
| 		} | ||||
| 		else if (IsA(lfirst(l), CurrentOfExpr)) | ||||
| 		{ | ||||
| 			/* CURRENT OF yields 1 tuple */ | ||||
| 			isCurrentOf = true; | ||||
| 			ntuples++; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			/* It's just CTID = something, count 1 tuple */ | ||||
| @ -800,6 +804,22 @@ cost_tidscan(Path *path, PlannerInfo *root, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * We must force TID scan for WHERE CURRENT OF, because only nodeTidscan.c | ||||
| 	 * understands how to do it correctly.  Therefore, honor enable_tidscan | ||||
| 	 * only when CURRENT OF isn't present.  Also note that cost_qual_eval | ||||
| 	 * counts a CurrentOfExpr as having startup cost disable_cost, which we | ||||
| 	 * subtract off here; that's to prevent other plan types such as seqscan | ||||
| 	 * from winning. | ||||
| 	 */ | ||||
| 	if (isCurrentOf) | ||||
| 	{ | ||||
| 		Assert(baserel->baserestrictcost.startup >= disable_cost); | ||||
| 		startup_cost -= disable_cost; | ||||
| 	} | ||||
| 	else if (!enable_tidscan) | ||||
| 		startup_cost += disable_cost; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The TID qual expressions will be computed once, any other baserestrict | ||||
| 	 * quals once per retrived tuple. | ||||
| @ -2002,8 +2022,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) | ||||
| 	} | ||||
| 	else if (IsA(node, CurrentOfExpr)) | ||||
| 	{ | ||||
| 		/* This is noticeably more expensive than a typical operator */ | ||||
| 		context->total.per_tuple += 100 * cpu_operator_cost; | ||||
| 		/* Report high cost to prevent selection of anything but TID scan */ | ||||
| 		context->total.startup += disable_cost; | ||||
| 	} | ||||
| 	else if (IsA(node, SubLink)) | ||||
| 	{ | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group | ||||
|  * Portions Copyright (c) 1994, Regents of the University of California | ||||
|  * | ||||
|  * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.178 2007/09/20 17:56:32 tgl Exp $ | ||||
|  * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.179 2007/10/24 18:37:08 tgl Exp $ | ||||
|  * | ||||
|  *------------------------------------------------------------------------- | ||||
|  */ | ||||
| @ -1087,6 +1087,7 @@ typedef struct BitmapHeapScanState | ||||
| /* ----------------
 | ||||
|  *	 TidScanState information | ||||
|  * | ||||
|  *		isCurrentOf	   scan has a CurrentOfExpr qual | ||||
|  *		NumTids		   number of tids in this scan | ||||
|  *		TidPtr		   index of currently fetched tid | ||||
|  *		TidList		   evaluated item pointers (array of size NumTids) | ||||
| @ -1096,6 +1097,7 @@ typedef struct TidScanState | ||||
| { | ||||
| 	ScanState	ss;				/* its first field is NodeTag */ | ||||
| 	List	   *tss_tidquals;	/* list of ExprState nodes */ | ||||
| 	bool		tss_isCurrentOf; | ||||
| 	int			tss_NumTids; | ||||
| 	int			tss_TidPtr; | ||||
| 	int			tss_MarkTidPtr; | ||||
|  | ||||
| @ -982,6 +982,139 @@ SELECT * FROM uctest; | ||||
|   8 | one | ||||
| (2 rows) | ||||
| 
 | ||||
| -- Check repeated-update and update-then-delete cases | ||||
| BEGIN; | ||||
| DECLARE c1 CURSOR FOR SELECT * FROM uctest; | ||||
| FETCH c1; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
| (1 row) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   8 | one | ||||
|  13 | three | ||||
| (2 rows) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   8 | one | ||||
|  23 | three | ||||
| (2 rows) | ||||
| 
 | ||||
| -- insensitive cursor should not show effects of updates or deletes | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
| (1 row) | ||||
| 
 | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 | f2   | ||||
| ----+----- | ||||
|   8 | one | ||||
| (1 row) | ||||
| 
 | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
|  f1 | f2   | ||||
| ----+----- | ||||
|   8 | one | ||||
| (1 row) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
|  f1 | f2   | ||||
| ----+----- | ||||
|   8 | one | ||||
| (1 row) | ||||
| 
 | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
| (1 row) | ||||
| 
 | ||||
| ROLLBACK; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
|   8 | one | ||||
| (2 rows) | ||||
| 
 | ||||
| BEGIN; | ||||
| DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; | ||||
| FETCH c1; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
| (1 row) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   8 | one | ||||
|  13 | three | ||||
| (2 rows) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   8 | one | ||||
|  23 | three | ||||
| (2 rows) | ||||
| 
 | ||||
| -- sensitive cursor should show effects of updates or deletes | ||||
| -- XXX current behavior is WRONG | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
|  f1 | f2   | ||||
| ----+----- | ||||
|   8 | one | ||||
| (1 row) | ||||
| 
 | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|  23 | three | ||||
| (1 row) | ||||
| 
 | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|  23 | three | ||||
| (1 row) | ||||
| 
 | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|  23 | three | ||||
| (1 row) | ||||
| 
 | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
|  f1 | f2  | ||||
| ----+---- | ||||
| (0 rows) | ||||
| 
 | ||||
| ROLLBACK; | ||||
| SELECT * FROM uctest; | ||||
|  f1 |  f2    | ||||
| ----+------- | ||||
|   3 | three | ||||
|   8 | one | ||||
| (2 rows) | ||||
| 
 | ||||
| -- Check inheritance cases | ||||
| CREATE TEMP TABLE ucchild () inherits (uctest); | ||||
| INSERT INTO ucchild values(100, 'hundred'); | ||||
|  | ||||
| @ -349,6 +349,46 @@ SELECT * FROM uctest; | ||||
| COMMIT; | ||||
| SELECT * FROM uctest; | ||||
| 
 | ||||
| -- Check repeated-update and update-then-delete cases | ||||
| BEGIN; | ||||
| DECLARE c1 CURSOR FOR SELECT * FROM uctest; | ||||
| FETCH c1; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| -- insensitive cursor should not show effects of updates or deletes | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
| ROLLBACK; | ||||
| SELECT * FROM uctest; | ||||
| 
 | ||||
| BEGIN; | ||||
| DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; | ||||
| FETCH c1; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| -- sensitive cursor should show effects of updates or deletes | ||||
| -- XXX current behavior is WRONG | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; | ||||
| SELECT * FROM uctest; | ||||
| DELETE FROM uctest WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
| UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op | ||||
| SELECT * FROM uctest; | ||||
| FETCH RELATIVE 0 FROM c1; | ||||
| ROLLBACK; | ||||
| SELECT * FROM uctest; | ||||
| 
 | ||||
| -- Check inheritance cases | ||||
| CREATE TEMP TABLE ucchild () inherits (uctest); | ||||
| INSERT INTO ucchild values(100, 'hundred'); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user