|
6 | 6 | * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
|
7 | 7 | * Portions Copyright (c) 1994, Regents of the University of California
|
8 | 8 | *
|
9 |
| - * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.7 2008/05/12 00:00:48 alvherre Exp $ |
| 9 | + * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.8 2008/11/16 17:34:28 tgl Exp $ |
10 | 10 | *
|
11 | 11 | *-------------------------------------------------------------------------
|
12 | 12 | */
|
@@ -46,10 +46,6 @@ execCurrentOf(CurrentOfExpr *cexpr,
|
46 | 46 | char *table_name;
|
47 | 47 | Portal portal;
|
48 | 48 | QueryDesc *queryDesc;
|
49 |
| - ScanState *scanstate; |
50 |
| - bool lisnull; |
51 |
| - Oid tuple_tableoid; |
52 |
| - ItemPointer tuple_tid; |
53 | 49 |
|
54 | 50 | /* Get the cursor name --- may have to look up a parameter reference */
|
55 | 51 | if (cexpr->cursor_name)
|
@@ -79,57 +75,129 @@ execCurrentOf(CurrentOfExpr *cexpr,
|
79 | 75 | errmsg("cursor \"%s\" is not a SELECT query",
|
80 | 76 | cursor_name)));
|
81 | 77 | queryDesc = PortalGetQueryDesc(portal);
|
82 |
| - if (queryDesc == NULL) |
| 78 | + if (queryDesc == NULL || queryDesc->estate == NULL) |
83 | 79 | ereport(ERROR,
|
84 | 80 | (errcode(ERRCODE_INVALID_CURSOR_STATE),
|
85 | 81 | errmsg("cursor \"%s\" is held from a previous transaction",
|
86 | 82 | cursor_name)));
|
87 | 83 |
|
88 | 84 | /*
|
89 |
| - * Dig through the cursor's plan to find the scan node. Fail if it's not |
90 |
| - * there or buried underneath aggregation. |
| 85 | + * We have two different strategies depending on whether the cursor uses |
| 86 | + * FOR UPDATE/SHARE or not. The reason for supporting both is that the |
| 87 | + * FOR UPDATE code is able to identify a target table in many cases where |
| 88 | + * the other code can't, while the non-FOR-UPDATE case allows use of WHERE |
| 89 | + * CURRENT OF with an insensitive cursor. |
91 | 90 | */
|
92 |
| - scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc), |
93 |
| - table_oid); |
94 |
| - if (!scanstate) |
95 |
| - ereport(ERROR, |
96 |
| - (errcode(ERRCODE_INVALID_CURSOR_STATE), |
97 |
| - errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", |
98 |
| - cursor_name, table_name))); |
| 91 | + if (queryDesc->estate->es_rowMarks) |
| 92 | + { |
| 93 | + ExecRowMark *erm; |
| 94 | + ListCell *lc; |
99 | 95 |
|
100 |
| - /* |
101 |
| - * The cursor must have a current result row: per the SQL spec, it's an |
102 |
| - * error if not. We test this at the top level, rather than at the scan |
103 |
| - * node level, because in inheritance cases any one table scan could |
104 |
| - * easily not be on a row. We want to return false, not raise error, if |
105 |
| - * the passed-in table OID is for one of the inactive scans. |
106 |
| - */ |
107 |
| - if (portal->atStart || portal->atEnd) |
108 |
| - ereport(ERROR, |
109 |
| - (errcode(ERRCODE_INVALID_CURSOR_STATE), |
110 |
| - errmsg("cursor \"%s\" is not positioned on a row", |
111 |
| - cursor_name))); |
| 96 | + /* |
| 97 | + * Here, the query must have exactly one FOR UPDATE/SHARE reference to |
| 98 | + * the target table, and we dig the ctid info out of that. |
| 99 | + */ |
| 100 | + erm = NULL; |
| 101 | + foreach(lc, queryDesc->estate->es_rowMarks) |
| 102 | + { |
| 103 | + ExecRowMark *thiserm = (ExecRowMark *) lfirst(lc); |
| 104 | + |
| 105 | + if (RelationGetRelid(thiserm->relation) == table_oid) |
| 106 | + { |
| 107 | + if (erm) |
| 108 | + ereport(ERROR, |
| 109 | + (errcode(ERRCODE_INVALID_CURSOR_STATE), |
| 110 | + errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"", |
| 111 | + cursor_name, table_name))); |
| 112 | + erm = thiserm; |
| 113 | + } |
| 114 | + } |
112 | 115 |
|
113 |
| - /* Now OK to return false if we found an inactive scan */ |
114 |
| - if (TupIsNull(scanstate->ss_ScanTupleSlot)) |
| 116 | + if (erm == NULL) |
| 117 | + ereport(ERROR, |
| 118 | + (errcode(ERRCODE_INVALID_CURSOR_STATE), |
| 119 | + errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"", |
| 120 | + cursor_name, table_name))); |
| 121 | + |
| 122 | + /* |
| 123 | + * The cursor must have a current result row: per the SQL spec, it's |
| 124 | + * an error if not. |
| 125 | + */ |
| 126 | + if (portal->atStart || portal->atEnd) |
| 127 | + ereport(ERROR, |
| 128 | + (errcode(ERRCODE_INVALID_CURSOR_STATE), |
| 129 | + errmsg("cursor \"%s\" is not positioned on a row", |
| 130 | + cursor_name))); |
| 131 | + |
| 132 | + /* Return the currently scanned TID, if there is one */ |
| 133 | + if (ItemPointerIsValid(&(erm->curCtid))) |
| 134 | + { |
| 135 | + *current_tid = erm->curCtid; |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + /* |
| 140 | + * This table didn't produce the cursor's current row; some other |
| 141 | + * inheritance child of the same parent must have. Signal caller |
| 142 | + * to do nothing on this table. |
| 143 | + */ |
115 | 144 | return false;
|
| 145 | + } |
| 146 | + else |
| 147 | + { |
| 148 | + ScanState *scanstate; |
| 149 | + bool lisnull; |
| 150 | + Oid tuple_tableoid; |
| 151 | + ItemPointer tuple_tid; |
| 152 | + |
| 153 | + /* |
| 154 | + * Without FOR UPDATE, we dig through the cursor's plan to find the |
| 155 | + * scan node. Fail if it's not there or buried underneath |
| 156 | + * aggregation. |
| 157 | + */ |
| 158 | + scanstate = search_plan_tree(ExecGetActivePlanTree(queryDesc), |
| 159 | + table_oid); |
| 160 | + if (!scanstate) |
| 161 | + ereport(ERROR, |
| 162 | + (errcode(ERRCODE_INVALID_CURSOR_STATE), |
| 163 | + errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", |
| 164 | + cursor_name, table_name))); |
116 | 165 |
|
117 |
| - /* Use slot_getattr to catch any possible mistakes */ |
118 |
| - tuple_tableoid = DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot, |
119 |
| - TableOidAttributeNumber, |
120 |
| - &lisnull)); |
121 |
| - Assert(!lisnull); |
122 |
| - tuple_tid = (ItemPointer) |
123 |
| - DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot, |
124 |
| - SelfItemPointerAttributeNumber, |
125 |
| - &lisnull)); |
126 |
| - Assert(!lisnull); |
| 166 | + /* |
| 167 | + * The cursor must have a current result row: per the SQL spec, it's |
| 168 | + * an error if not. We test this at the top level, rather than at the |
| 169 | + * scan node level, because in inheritance cases any one table scan |
| 170 | + * could easily not be on a row. We want to return false, not raise |
| 171 | + * error, if the passed-in table OID is for one of the inactive scans. |
| 172 | + */ |
| 173 | + if (portal->atStart || portal->atEnd) |
| 174 | + ereport(ERROR, |
| 175 | + (errcode(ERRCODE_INVALID_CURSOR_STATE), |
| 176 | + errmsg("cursor \"%s\" is not positioned on a row", |
| 177 | + cursor_name))); |
127 | 178 |
|
128 |
| - Assert(tuple_tableoid == table_oid); |
| 179 | + /* Now OK to return false if we found an inactive scan */ |
| 180 | + if (TupIsNull(scanstate->ss_ScanTupleSlot)) |
| 181 | + return false; |
129 | 182 |
|
130 |
| - *current_tid = *tuple_tid; |
| 183 | + /* Use slot_getattr to catch any possible mistakes */ |
| 184 | + tuple_tableoid = |
| 185 | + DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot, |
| 186 | + TableOidAttributeNumber, |
| 187 | + &lisnull)); |
| 188 | + Assert(!lisnull); |
| 189 | + tuple_tid = (ItemPointer) |
| 190 | + DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot, |
| 191 | + SelfItemPointerAttributeNumber, |
| 192 | + &lisnull)); |
| 193 | + Assert(!lisnull); |
131 | 194 |
|
132 |
| - return true; |
| 195 | + Assert(tuple_tableoid == table_oid); |
| 196 | + |
| 197 | + *current_tid = *tuple_tid; |
| 198 | + |
| 199 | + return true; |
| 200 | + } |
133 | 201 | }
|
134 | 202 |
|
135 | 203 | /*
|
|
0 commit comments