]> granicus.if.org Git - postgresql/blob - src/backend/utils/mmgr/portalmem.c
Code review for holdable-cursors patch. Fix error recovery, memory
[postgresql] / src / backend / utils / mmgr / portalmem.c
1 /*-------------------------------------------------------------------------
2  *
3  * portalmem.c
4  *        backend portal memory context management stuff
5  *
6  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.55 2003/04/29 03:21:29 tgl Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 /*
16  * NOTES
17  *              A "Portal" is a structure used to keep track of cursor queries.
18  *
19  *              When the backend sees a "declare cursor" query, it allocates a
20  *              "PortalData" structure, plans the query and then stores the query
21  *              in the portal without executing it.  Later, when the backend
22  *              sees a
23  *                              fetch 1 from foo
24  *              the system looks up the portal named "foo" in the portal table,
25  *              gets the planned query and then calls the executor with a count
26  *              of 1.  The executor then runs the query and returns a single
27  *              tuple.  The problem is that we have to hold onto the state of the
28  *              portal query until we see a "close".  This means we have to be
29  *              careful about memory management.
30  *
31  *              I hope this makes things clearer to whoever reads this -cim 2/22/91
32  */
33
34 #include "postgres.h"
35
36 #include "commands/portalcmds.h"
37 #include "executor/executor.h"
38 #include "utils/hsearch.h"
39 #include "utils/memutils.h"
40 #include "utils/portal.h"
41
42 /*
43  * estimate of the maximum number of open portals a user would have,
44  * used in initially sizing the PortalHashTable in EnablePortalManager()
45  */
46 #define PORTALS_PER_USER           64
47
48
49 /* ----------------
50  *              Global state
51  * ----------------
52  */
53
54 #define MAX_PORTALNAME_LEN              NAMEDATALEN
55
56 typedef struct portalhashent
57 {
58         char            portalname[MAX_PORTALNAME_LEN];
59         Portal          portal;
60 } PortalHashEnt;
61
62 static HTAB *PortalHashTable = NULL;
63
64 #define PortalHashTableLookup(NAME, PORTAL) \
65 do { \
66         PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
67         \
68         MemSet(key, 0, MAX_PORTALNAME_LEN); \
69         snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \
70         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
71                                                                                  key, HASH_FIND, NULL); \
72         if (hentry) \
73                 PORTAL = hentry->portal; \
74         else \
75                 PORTAL = NULL; \
76 } while(0)
77
78 #define PortalHashTableInsert(PORTAL) \
79 do { \
80         PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
81         \
82         MemSet(key, 0, MAX_PORTALNAME_LEN); \
83         snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
84         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
85                                                                                  key, HASH_ENTER, &found); \
86         if (hentry == NULL) \
87                 elog(ERROR, "out of memory in PortalHashTable"); \
88         if (found) \
89                 elog(WARNING, "trying to insert a portal name that exists."); \
90         hentry->portal = PORTAL; \
91 } while(0)
92
93 #define PortalHashTableDelete(PORTAL) \
94 do { \
95         PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
96         \
97         MemSet(key, 0, MAX_PORTALNAME_LEN); \
98         snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
99         hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
100                                                                                  key, HASH_REMOVE, NULL); \
101         if (hentry == NULL) \
102                 elog(WARNING, "trying to delete portal name that does not exist."); \
103 } while(0)
104
105 static MemoryContext PortalMemory = NULL;
106
107
108 /* ----------------------------------------------------------------
109  *                                 public portal interface functions
110  * ----------------------------------------------------------------
111  */
112
113 /*
114  * EnablePortalManager
115  *              Enables the portal management module at backend startup.
116  */
117 void
118 EnablePortalManager(void)
119 {
120         HASHCTL         ctl;
121
122         Assert(PortalMemory == NULL);
123
124         PortalMemory = AllocSetContextCreate(TopMemoryContext,
125                                                                                  "PortalMemory",
126                                                                                  ALLOCSET_DEFAULT_MINSIZE,
127                                                                                  ALLOCSET_DEFAULT_INITSIZE,
128                                                                                  ALLOCSET_DEFAULT_MAXSIZE);
129
130         ctl.keysize = MAX_PORTALNAME_LEN;
131         ctl.entrysize = sizeof(PortalHashEnt);
132
133         /*
134          * use PORTALS_PER_USER as a guess of how many hash table entries to
135          * create, initially
136          */
137         PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
138                                                                   &ctl, HASH_ELEM);
139 }
140
141 /*
142  * GetPortalByName
143  *              Returns a portal given a portal name, or NULL if name not found.
144  */
145 Portal
146 GetPortalByName(const char *name)
147 {
148         Portal          portal;
149
150         if (PointerIsValid(name))
151                 PortalHashTableLookup(name, portal);
152         else
153                 portal = NULL;
154
155         return portal;
156 }
157
158 /*
159  * PortalSetQuery
160  *              Attaches a QueryDesc to the specified portal.  This should be
161  *              called only after successfully doing ExecutorStart for the query.
162  *
163  * Note that in the case of DECLARE CURSOR, some Portal options have
164  * already been set in portalcmds.c's PreparePortal().  This is grotty.
165  */
166 void
167 PortalSetQuery(Portal portal, QueryDesc *queryDesc)
168 {
169         AssertArg(PortalIsValid(portal));
170
171         /*
172          * If the user didn't specify a SCROLL type, allow or disallow
173          * scrolling based on whether it would require any additional
174          * runtime overhead to do so.
175          */
176         if (portal->scrollType == DEFAULT_SCROLL)
177         {
178                 if (ExecSupportsBackwardScan(queryDesc->plantree))
179                         portal->scrollType = ENABLE_SCROLL;
180                 else
181                         portal->scrollType = DISABLE_SCROLL;
182         }
183
184         portal->queryDesc = queryDesc;
185         portal->executorRunning = true; /* now need to shut down executor */
186
187         portal->atStart = true;
188         portal->atEnd = false;          /* allow fetches */
189         portal->portalPos = 0;
190         portal->posOverflow = false;
191 }
192
193 /*
194  * CreatePortal
195  *              Returns a new portal given a name.
196  *
197  *              An elog(WARNING) is emitted if portal name is in use (existing
198  *              portal is returned!)
199  */
200 Portal
201 CreatePortal(const char *name)
202 {
203         Portal          portal;
204
205         AssertArg(PointerIsValid(name));
206
207         portal = GetPortalByName(name);
208         if (PortalIsValid(portal))
209         {
210                 elog(WARNING, "CreatePortal: portal \"%s\" already exists", name);
211                 return portal;
212         }
213
214         /* make new portal structure */
215         portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal);
216
217         /* initialize portal name */
218         portal->name = MemoryContextStrdup(PortalMemory, name);
219
220         /* initialize portal heap context */
221         portal->heap = AllocSetContextCreate(PortalMemory,
222                                                                                  "PortalHeapMemory",
223                                                                                  ALLOCSET_DEFAULT_MINSIZE,
224                                                                                  ALLOCSET_DEFAULT_INITSIZE,
225                                                                                  ALLOCSET_DEFAULT_MAXSIZE);
226
227         /* initialize portal query */
228         portal->queryDesc = NULL;
229         portal->cleanup = PortalCleanup;
230         portal->scrollType = DEFAULT_SCROLL;
231         portal->executorRunning = false;
232         portal->holdOpen = false;
233         portal->createXact = GetCurrentTransactionId();
234         portal->holdStore = NULL;
235         portal->holdContext = NULL;
236         portal->atStart = true;
237         portal->atEnd = true;           /* disallow fetches until query is set */
238         portal->portalPos = 0;
239         portal->posOverflow = false;
240
241         /* put portal in table */
242         PortalHashTableInsert(portal);
243
244         return portal;
245 }
246
247 /*
248  * PortalDrop
249  *              Destroy the portal.
250  *
251  *              isError: if true, we are destroying portals at the end of a failed
252  *              transaction.  (This causes PortalCleanup to skip unneeded steps.)
253  */
254 void
255 PortalDrop(Portal portal, bool isError)
256 {
257         AssertArg(PortalIsValid(portal));
258
259         /*
260          * Remove portal from hash table.  Because we do this first, we will
261          * not come back to try to remove the portal again if there's any error
262          * in the subsequent steps.  Better to leak a little memory than to get
263          * into an infinite error-recovery loop.
264          */
265         PortalHashTableDelete(portal);
266
267         /* let portalcmds.c clean up the state it knows about */
268         if (PointerIsValid(portal->cleanup))
269                 (*portal->cleanup) (portal, isError);
270
271         /* delete tuplestore storage, if any */
272         if (portal->holdContext)
273                 MemoryContextDelete(portal->holdContext);
274
275         /* release subsidiary storage */
276         if (PortalGetHeapMemory(portal))
277                 MemoryContextDelete(PortalGetHeapMemory(portal));
278
279         /* release name and portal data (both are in PortalMemory) */
280         pfree(portal->name);
281         pfree(portal);
282 }
283
284 /*
285  * Cleanup the portals created in the current transaction. If the
286  * transaction was aborted, all the portals created in this transaction
287  * should be removed. If the transaction was successfully committed, any
288  * holdable cursors created in this transaction need to be kept
289  * open. In any case, portals remaining from prior transactions should
290  * be left untouched.
291  *
292  * XXX This assumes that portals can be deleted in a random order, ie,
293  * no portal has a reference to any other (at least not one that will be
294  * exercised during deletion).  I think this is okay at the moment, but
295  * we've had bugs of that ilk in the past.  Keep a close eye on cursor
296  * references...
297  */
298 void
299 AtEOXact_portals(bool isCommit)
300 {
301         HASH_SEQ_STATUS status;
302         PortalHashEnt *hentry;
303         TransactionId xact = GetCurrentTransactionId();
304
305         hash_seq_init(&status, PortalHashTable);
306
307         while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
308         {
309                 Portal portal = hentry->portal;
310
311                 if (portal->createXact != xact)
312                         continue;
313
314                 if (portal->holdOpen && isCommit)
315                 {
316                         /*
317                          * We are exiting the transaction that created a holdable
318                          * cursor.  Instead of dropping the portal, prepare it for
319                          * access by later transactions.
320                          */
321
322                         /*
323                          * Create the memory context that is used for storage of
324                          * the held cursor's tuple set.
325                          */
326                         portal->holdContext =
327                                 AllocSetContextCreate(PortalMemory,
328                                                                           "PortalHeapMemory",
329                                                                           ALLOCSET_DEFAULT_MINSIZE,
330                                                                           ALLOCSET_DEFAULT_INITSIZE,
331                                                                           ALLOCSET_DEFAULT_MAXSIZE);
332
333                         /*
334                          * Transfer data into the held tuplestore.
335                          *
336                          * Note that PersistHoldablePortal() must release all
337                          * resources used by the portal that are local to the creating
338                          * transaction.
339                          */
340                         PersistHoldablePortal(portal);
341                 }
342                 else
343                 {
344                         PortalDrop(portal, !isCommit);
345                 }
346         }
347 }