Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * tuplestore.c
4 : : * Generalized routines for temporary tuple storage.
5 : : *
6 : : * This module handles temporary storage of tuples for purposes such
7 : : * as Materialize nodes, hashjoin batch files, etc. It is essentially
8 : : * a dumbed-down version of tuplesort.c; it does no sorting of tuples
9 : : * but can only store and regurgitate a sequence of tuples. However,
10 : : * because no sort is required, it is allowed to start reading the sequence
11 : : * before it has all been written. This is particularly useful for cursors,
12 : : * because it allows random access within the already-scanned portion of
13 : : * a query without having to process the underlying scan to completion.
14 : : * Also, it is possible to support multiple independent read pointers.
15 : : *
16 : : * A temporary file is used to handle the data if it exceeds the
17 : : * space limit specified by the caller.
18 : : *
19 : : * The (approximate) amount of memory allowed to the tuplestore is specified
20 : : * in kilobytes by the caller. We absorb tuples and simply store them in an
21 : : * in-memory array as long as we haven't exceeded maxKBytes. If we do exceed
22 : : * maxKBytes, we dump all the tuples into a temp file and then read from that
23 : : * when needed.
24 : : *
25 : : * Upon creation, a tuplestore supports a single read pointer, numbered 0.
26 : : * Additional read pointers can be created using tuplestore_alloc_read_pointer.
27 : : * Mark/restore behavior is supported by copying read pointers.
28 : : *
29 : : * When the caller requests backward-scan capability, we write the temp file
30 : : * in a format that allows either forward or backward scan. Otherwise, only
31 : : * forward scan is allowed. A request for backward scan must be made before
32 : : * putting any tuples into the tuplestore. Rewind is normally allowed but
33 : : * can be turned off via tuplestore_set_eflags; turning off rewind for all
34 : : * read pointers enables truncation of the tuplestore at the oldest read point
35 : : * for minimal memory usage. (The caller must explicitly call tuplestore_trim
36 : : * at appropriate times for truncation to actually happen.)
37 : : *
38 : : * Note: in TSS_WRITEFILE state, the temp file's seek position is the
39 : : * current write position, and the write-position variables in the tuplestore
40 : : * aren't kept up to date. Similarly, in TSS_READFILE state the temp file's
41 : : * seek position is the active read pointer's position, and that read pointer
42 : : * isn't kept up to date. We update the appropriate variables using ftell()
43 : : * before switching to the other state or activating a different read pointer.
44 : : *
45 : : *
46 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
47 : : * Portions Copyright (c) 1994, Regents of the University of California
48 : : *
49 : : * IDENTIFICATION
50 : : * src/backend/utils/sort/tuplestore.c
51 : : *
52 : : *-------------------------------------------------------------------------
53 : : */
54 : :
55 : : #include "postgres.h"
56 : :
57 : : #include <limits.h>
58 : :
59 : : #include "access/htup_details.h"
60 : : #include "commands/tablespace.h"
61 : : #include "executor/executor.h"
62 : : #include "miscadmin.h"
63 : : #include "storage/buffile.h"
64 : : #include "utils/memutils.h"
65 : : #include "utils/resowner.h"
66 : : #include "utils/tuplestore.h"
67 : :
68 : :
69 : : /*
70 : : * Possible states of a Tuplestore object. These denote the states that
71 : : * persist between calls of Tuplestore routines.
72 : : */
73 : : typedef enum
74 : : {
75 : : TSS_INMEM, /* Tuples still fit in memory */
76 : : TSS_WRITEFILE, /* Writing to temp file */
77 : : TSS_READFILE, /* Reading from temp file */
78 : : } TupStoreStatus;
79 : :
80 : : /*
81 : : * State for a single read pointer. If we are in state INMEM then all the
82 : : * read pointers' "current" fields denote the read positions. In state
83 : : * WRITEFILE, the file/offset fields denote the read positions. In state
84 : : * READFILE, inactive read pointers have valid file/offset, but the active
85 : : * read pointer implicitly has position equal to the temp file's seek position.
86 : : *
87 : : * Special case: if eof_reached is true, then the pointer's read position is
88 : : * implicitly equal to the write position, and current/file/offset aren't
89 : : * maintained. This way we need not update all the read pointers each time
90 : : * we write.
91 : : */
92 : : typedef struct
93 : : {
94 : : int eflags; /* capability flags */
95 : : bool eof_reached; /* read has reached EOF */
96 : : int current; /* next array index to read */
97 : : int file; /* temp file# */
98 : : pgoff_t offset; /* byte offset in file */
99 : : } TSReadPointer;
100 : :
101 : : /*
102 : : * Private state of a Tuplestore operation.
103 : : */
104 : : struct Tuplestorestate
105 : : {
106 : : TupStoreStatus status; /* enumerated value as shown above */
107 : : int eflags; /* capability flags (OR of pointers' flags) */
108 : : bool backward; /* store extra length words in file? */
109 : : bool interXact; /* keep open through transactions? */
110 : : bool truncated; /* tuplestore_trim has removed tuples? */
111 : : bool usedDisk; /* used by tuplestore_get_stats() */
112 : : int64 maxSpace; /* used by tuplestore_get_stats() */
113 : : int64 availMem; /* remaining memory available, in bytes */
114 : : int64 allowedMem; /* total memory allowed, in bytes */
115 : : int64 tuples; /* number of tuples added */
116 : : BufFile *myfile; /* underlying file, or NULL if none */
117 : : MemoryContext context; /* memory context for holding tuples */
118 : : ResourceOwner resowner; /* resowner for holding temp files */
119 : :
120 : : /*
121 : : * These function pointers decouple the routines that must know what kind
122 : : * of tuple we are handling from the routines that don't need to know it.
123 : : * They are set up by the tuplestore_begin_xxx routines.
124 : : *
125 : : * (Although tuplestore.c currently only supports heap tuples, I've copied
126 : : * this part of tuplesort.c so that extension to other kinds of objects
127 : : * will be easy if it's ever needed.)
128 : : *
129 : : * Function to copy a supplied input tuple into palloc'd space. (NB: we
130 : : * assume that a single pfree() is enough to release the tuple later, so
131 : : * the representation must be "flat" in one palloc chunk.) state->availMem
132 : : * must be decreased by the amount of space used.
133 : : */
134 : : void *(*copytup) (Tuplestorestate *state, void *tup);
135 : :
136 : : /*
137 : : * Function to write a stored tuple onto tape. The representation of the
138 : : * tuple on tape need not be the same as it is in memory; requirements on
139 : : * the tape representation are given below. After writing the tuple,
140 : : * pfree() it, and increase state->availMem by the amount of memory space
141 : : * thereby released.
142 : : */
143 : : void (*writetup) (Tuplestorestate *state, void *tup);
144 : :
145 : : /*
146 : : * Function to read a stored tuple from tape back into memory. 'len' is
147 : : * the already-read length of the stored tuple. Create and return a
148 : : * palloc'd copy, and decrease state->availMem by the amount of memory
149 : : * space consumed.
150 : : */
151 : : void *(*readtup) (Tuplestorestate *state, unsigned int len);
152 : :
153 : : /*
154 : : * This array holds pointers to tuples in memory if we are in state INMEM.
155 : : * In states WRITEFILE and READFILE it's not used.
156 : : *
157 : : * When memtupdeleted > 0, the first memtupdeleted pointers are already
158 : : * released due to a tuplestore_trim() operation, but we haven't expended
159 : : * the effort to slide the remaining pointers down. These unused pointers
160 : : * are set to NULL to catch any invalid accesses. Note that memtupcount
161 : : * includes the deleted pointers.
162 : : */
163 : : void **memtuples; /* array of pointers to palloc'd tuples */
164 : : int memtupdeleted; /* the first N slots are currently unused */
165 : : int memtupcount; /* number of tuples currently present */
166 : : int memtupsize; /* allocated length of memtuples array */
167 : : bool growmemtuples; /* memtuples' growth still underway? */
168 : :
169 : : /*
170 : : * These variables are used to keep track of the current positions.
171 : : *
172 : : * In state WRITEFILE, the current file seek position is the write point;
173 : : * in state READFILE, the write position is remembered in writepos_xxx.
174 : : * (The write position is the same as EOF, but since BufFileSeek doesn't
175 : : * currently implement SEEK_END, we have to remember it explicitly.)
176 : : */
177 : : TSReadPointer *readptrs; /* array of read pointers */
178 : : int activeptr; /* index of the active read pointer */
179 : : int readptrcount; /* number of pointers currently valid */
180 : : int readptrsize; /* allocated length of readptrs array */
181 : :
182 : : int writepos_file; /* file# (valid if READFILE state) */
183 : : pgoff_t writepos_offset; /* offset (valid if READFILE state) */
184 : : };
185 : :
186 : : #define COPYTUP(state,tup) ((*(state)->copytup) (state, tup))
187 : : #define WRITETUP(state,tup) ((*(state)->writetup) (state, tup))
188 : : #define READTUP(state,len) ((*(state)->readtup) (state, len))
189 : : #define LACKMEM(state) ((state)->availMem < 0)
190 : : #define USEMEM(state,amt) ((state)->availMem -= (amt))
191 : : #define FREEMEM(state,amt) ((state)->availMem += (amt))
192 : :
193 : : /*--------------------
194 : : *
195 : : * NOTES about on-tape representation of tuples:
196 : : *
197 : : * We require the first "unsigned int" of a stored tuple to be the total size
198 : : * on-tape of the tuple, including itself (so it is never zero).
199 : : * The remainder of the stored tuple
200 : : * may or may not match the in-memory representation of the tuple ---
201 : : * any conversion needed is the job of the writetup and readtup routines.
202 : : *
203 : : * If state->backward is true, then the stored representation of
204 : : * the tuple must be followed by another "unsigned int" that is a copy of the
205 : : * length --- so the total tape space used is actually sizeof(unsigned int)
206 : : * more than the stored length value. This allows read-backwards. When
207 : : * state->backward is not set, the write/read routines may omit the extra
208 : : * length word.
209 : : *
210 : : * writetup is expected to write both length words as well as the tuple
211 : : * data. When readtup is called, the tape is positioned just after the
212 : : * front length word; readtup must read the tuple data and advance past
213 : : * the back length word (if present).
214 : : *
215 : : * The write/read routines can make use of the tuple description data
216 : : * stored in the Tuplestorestate record, if needed. They are also expected
217 : : * to adjust state->availMem by the amount of memory space (not tape space!)
218 : : * released or consumed. There is no error return from either writetup
219 : : * or readtup; they should ereport() on failure.
220 : : *
221 : : *
222 : : * NOTES about memory consumption calculations:
223 : : *
224 : : * We count space allocated for tuples against the maxKBytes limit,
225 : : * plus the space used by the variable-size array memtuples.
226 : : * Fixed-size space (primarily the BufFile I/O buffer) is not counted.
227 : : * We don't worry about the size of the read pointer array, either.
228 : : *
229 : : * Note that we count actual space used (as shown by GetMemoryChunkSpace)
230 : : * rather than the originally-requested size. This is important since
231 : : * palloc can add substantial overhead. It's not a complete answer since
232 : : * we won't count any wasted space in palloc allocation blocks, but it's
233 : : * a lot better than what we were doing before 7.3.
234 : : *
235 : : *--------------------
236 : : */
237 : :
238 : :
239 : : static Tuplestorestate *tuplestore_begin_common(int eflags,
240 : : bool interXact,
241 : : int maxKBytes);
242 : : static void tuplestore_puttuple_common(Tuplestorestate *state, void *tuple);
243 : : static void dumptuples(Tuplestorestate *state);
244 : : static void tuplestore_updatemax(Tuplestorestate *state);
245 : : static unsigned int getlen(Tuplestorestate *state, bool eofOK);
246 : : static void *copytup_heap(Tuplestorestate *state, void *tup);
247 : : static void writetup_heap(Tuplestorestate *state, void *tup);
248 : : static void *readtup_heap(Tuplestorestate *state, unsigned int len);
249 : :
250 : :
251 : : /*
252 : : * tuplestore_begin_xxx
253 : : *
254 : : * Initialize for a tuple store operation.
255 : : */
256 : : static Tuplestorestate *
7038 tgl@sss.pgh.pa.us 257 :CBC 148973 : tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
258 : : {
259 : : Tuplestorestate *state;
260 : :
260 michael@paquier.xyz 261 : 148973 : state = palloc0_object(Tuplestorestate);
262 : :
8572 tgl@sss.pgh.pa.us 263 : 148973 : state->status = TSS_INMEM;
7038 264 : 148973 : state->eflags = eflags;
8521 265 : 148973 : state->interXact = interXact;
6452 266 : 148973 : state->truncated = false;
714 drowley@postgresql.o 267 : 148973 : state->usedDisk = false;
268 : 148973 : state->maxSpace = 0;
573 tgl@sss.pgh.pa.us 269 : 148973 : state->allowedMem = maxKBytes * (int64) 1024;
4970 270 : 148973 : state->availMem = state->allowedMem;
9566 271 : 148973 : state->myfile = NULL;
272 : :
273 : : /*
274 : : * The palloc/pfree pattern for tuple memory is in a FIFO pattern. A
275 : : * generation context is perfectly suited for this.
276 : : */
783 drowley@postgresql.o 277 : 148973 : state->context = GenerationContextCreate(CurrentMemoryContext,
278 : : "tuplestore tuples",
279 : : ALLOCSET_DEFAULT_SIZES);
6085 heikki.linnakangas@i 280 : 148973 : state->resowner = CurrentResourceOwner;
281 : :
5739 tgl@sss.pgh.pa.us 282 : 148973 : state->memtupdeleted = 0;
9566 283 : 148973 : state->memtupcount = 0;
3436 kgrittn@postgresql.o 284 : 148973 : state->tuples = 0;
285 : :
286 : : /*
287 : : * Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
288 : : * see comments in grow_memtuples().
289 : : */
4041 tgl@sss.pgh.pa.us 290 : 148973 : state->memtupsize = Max(16384 / sizeof(void *),
291 : : ALLOCSET_SEPARATE_THRESHOLD / sizeof(void *) + 1);
292 : :
4970 293 : 148973 : state->growmemtuples = true;
10 michael@paquier.xyz 294 :GNC 148973 : state->memtuples = palloc_array(void *, state->memtupsize);
295 : :
8781 tgl@sss.pgh.pa.us 296 :CBC 148973 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
297 : :
6539 298 : 148973 : state->activeptr = 0;
299 : 148973 : state->readptrcount = 1;
300 : 148973 : state->readptrsize = 8; /* arbitrary */
10 michael@paquier.xyz 301 :GNC 148973 : state->readptrs = palloc_array(TSReadPointer, state->readptrsize);
302 : :
6539 tgl@sss.pgh.pa.us 303 :CBC 148973 : state->readptrs[0].eflags = eflags;
304 : 148973 : state->readptrs[0].eof_reached = false;
305 : 148973 : state->readptrs[0].current = 0;
306 : :
9566 307 : 148973 : return state;
308 : : }
309 : :
310 : : /*
311 : : * tuplestore_begin_heap
312 : : *
313 : : * Create a new tuplestore; other types of tuple stores (other than
314 : : * "heap" tuple stores, for heap tuples) are possible, but not presently
315 : : * implemented.
316 : : *
317 : : * randomAccess: if true, both forward and backward accesses to the
318 : : * tuple store are allowed.
319 : : *
320 : : * interXact: if true, the files used for on-disk storage persist beyond the
321 : : * end of the current transaction. NOTE: It's the caller's responsibility to
322 : : * create such a tuplestore in a memory context and resource owner that will
323 : : * also survive transaction boundaries, and to ensure the tuplestore is closed
324 : : * when it's no longer wanted.
325 : : *
326 : : * maxKBytes: how much data to store in memory (any data beyond this
327 : : * amount is paged to disk). When in doubt, use work_mem.
328 : : */
329 : : Tuplestorestate *
8521 330 : 148973 : tuplestore_begin_heap(bool randomAccess, bool interXact, int maxKBytes)
331 : : {
332 : : Tuplestorestate *state;
333 : : int eflags;
334 : :
335 : : /*
336 : : * This interpretation of the meaning of randomAccess is compatible with
337 : : * the pre-8.3 behavior of tuplestores.
338 : : */
7038 339 : 148973 : eflags = randomAccess ?
6539 340 [ + + ]: 148973 : (EXEC_FLAG_BACKWARD | EXEC_FLAG_REWIND) :
341 : : (EXEC_FLAG_REWIND);
342 : :
7038 343 : 148973 : state = tuplestore_begin_common(eflags, interXact, maxKBytes);
344 : :
9566 345 : 148973 : state->copytup = copytup_heap;
346 : 148973 : state->writetup = writetup_heap;
347 : 148973 : state->readtup = readtup_heap;
348 : :
349 : 148973 : return state;
350 : : }
351 : :
352 : : /*
353 : : * tuplestore_set_eflags
354 : : *
355 : : * Set the capability flags for read pointer 0 at a finer grain than is
356 : : * allowed by tuplestore_begin_xxx. This must be called before inserting
357 : : * any data into the tuplestore.
358 : : *
359 : : * eflags is a bitmask following the meanings used for executor node
360 : : * startup flags (see executor.h). tuplestore pays attention to these bits:
361 : : * EXEC_FLAG_REWIND need rewind to start
362 : : * EXEC_FLAG_BACKWARD need backward fetch
363 : : * If tuplestore_set_eflags is not called, REWIND is allowed, and BACKWARD
364 : : * is set per "randomAccess" in the tuplestore_begin_xxx call.
365 : : *
366 : : * NOTE: setting BACKWARD without REWIND means the pointer can read backwards,
367 : : * but not further than the truncation point (the furthest-back read pointer
368 : : * position at the time of the last tuplestore_trim call).
369 : : */
370 : : void
7038 371 : 5156 : tuplestore_set_eflags(Tuplestorestate *state, int eflags)
372 : : {
373 : : int i;
374 : :
6539 375 [ + - - + ]: 5156 : if (state->status != TSS_INMEM || state->memtupcount != 0)
6539 tgl@sss.pgh.pa.us 376 [ # # ]:UBC 0 : elog(ERROR, "too late to call tuplestore_set_eflags");
377 : :
6539 tgl@sss.pgh.pa.us 378 :CBC 5156 : state->readptrs[0].eflags = eflags;
379 [ - + ]: 5156 : for (i = 1; i < state->readptrcount; i++)
6539 tgl@sss.pgh.pa.us 380 :UBC 0 : eflags |= state->readptrs[i].eflags;
7038 tgl@sss.pgh.pa.us 381 :CBC 5156 : state->eflags = eflags;
382 : 5156 : }
383 : :
384 : : /*
385 : : * tuplestore_alloc_read_pointer - allocate another read pointer.
386 : : *
387 : : * Returns the pointer's index.
388 : : *
389 : : * The new pointer initially copies the position of read pointer 0.
390 : : * It can have its own eflags, but if any data has been inserted into
391 : : * the tuplestore, these eflags must not represent an increase in
392 : : * requirements.
393 : : */
394 : : int
6539 395 : 6802 : tuplestore_alloc_read_pointer(Tuplestorestate *state, int eflags)
396 : : {
397 : : /* Check for possible increase of requirements */
398 [ + - + + ]: 6802 : if (state->status != TSS_INMEM || state->memtupcount != 0)
399 : : {
400 [ - + ]: 498 : if ((state->eflags | eflags) != state->eflags)
6539 tgl@sss.pgh.pa.us 401 [ # # ]:UBC 0 : elog(ERROR, "too late to require new tuplestore eflags");
402 : : }
403 : :
404 : : /* Make room for another read pointer if needed */
6539 tgl@sss.pgh.pa.us 405 [ + + ]:CBC 6802 : if (state->readptrcount >= state->readptrsize)
406 : : {
6286 bruce@momjian.us 407 : 20 : int newcnt = state->readptrsize * 2;
408 : :
10 michael@paquier.xyz 409 :GNC 20 : state->readptrs = repalloc_array(state->readptrs, TSReadPointer, newcnt);
6539 tgl@sss.pgh.pa.us 410 :CBC 20 : state->readptrsize = newcnt;
411 : : }
412 : :
413 : : /* And set it up */
414 : 6802 : state->readptrs[state->readptrcount] = state->readptrs[0];
415 : 6802 : state->readptrs[state->readptrcount].eflags = eflags;
416 : :
417 : 6802 : state->eflags |= eflags;
418 : :
419 : 6802 : return state->readptrcount++;
420 : : }
421 : :
422 : : /*
423 : : * tuplestore_clear
424 : : *
425 : : * Delete all the contents of a tuplestore, and reset its read pointers
426 : : * to the start.
427 : : */
428 : : void
6536 429 : 6989 : tuplestore_clear(Tuplestorestate *state)
430 : : {
431 : : int i;
432 : : TSReadPointer *readptr;
433 : :
434 : : /* update the maxSpace before doing any USEMEM/FREEMEM adjustments */
783 drowley@postgresql.o 435 : 6989 : tuplestore_updatemax(state);
436 : :
6536 tgl@sss.pgh.pa.us 437 [ + + ]: 6989 : if (state->myfile)
438 : 8 : BufFileClose(state->myfile);
439 : 6989 : state->myfile = NULL;
440 : :
441 : : #ifdef USE_ASSERT_CHECKING
442 : : {
783 drowley@postgresql.o 443 : 6989 : int64 availMem = state->availMem;
444 : :
445 : : /*
446 : : * Below, we reset the memory context for storing tuples. To save
447 : : * from having to always call GetMemoryChunkSpace() on all stored
448 : : * tuples, we adjust the availMem to forget all the tuples and just
449 : : * recall USEMEM for the space used by the memtuples array. Here we
450 : : * just Assert that's correct and the memory tracking hasn't gone
451 : : * wrong anywhere.
452 : : */
5739 tgl@sss.pgh.pa.us 453 [ + + ]: 72338 : for (i = state->memtupdeleted; i < state->memtupcount; i++)
783 drowley@postgresql.o 454 : 65349 : availMem += GetMemoryChunkSpace(state->memtuples[i]);
455 : :
456 : 6989 : availMem += GetMemoryChunkSpace(state->memtuples);
457 : :
458 [ - + ]: 6989 : Assert(availMem == state->allowedMem);
459 : : }
460 : : #endif
461 : :
462 : : /* clear the memory consumed by the memory tuples */
463 : 6989 : MemoryContextReset(state->context);
464 : :
465 : : /*
466 : : * Zero the used memory and re-consume the space for the memtuples array.
467 : : * This saves having to FREEMEM for each stored tuple.
468 : : */
469 : 6989 : state->availMem = state->allowedMem;
470 : 6989 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
471 : :
6536 tgl@sss.pgh.pa.us 472 : 6989 : state->status = TSS_INMEM;
6452 473 : 6989 : state->truncated = false;
5739 474 : 6989 : state->memtupdeleted = 0;
6536 475 : 6989 : state->memtupcount = 0;
3436 kgrittn@postgresql.o 476 : 6989 : state->tuples = 0;
6536 tgl@sss.pgh.pa.us 477 : 6989 : readptr = state->readptrs;
478 [ + + ]: 21788 : for (i = 0; i < state->readptrcount; readptr++, i++)
479 : : {
480 : 14799 : readptr->eof_reached = false;
481 : 14799 : readptr->current = 0;
482 : : }
483 : 6989 : }
484 : :
485 : : /*
486 : : * tuplestore_end
487 : : *
488 : : * Release resources and clean up.
489 : : */
490 : : void
9566 491 : 148335 : tuplestore_end(Tuplestorestate *state)
492 : : {
493 [ + + ]: 148335 : if (state->myfile)
494 : 84 : BufFileClose(state->myfile);
495 : :
783 drowley@postgresql.o 496 : 148335 : MemoryContextDelete(state->context);
497 : 148335 : pfree(state->memtuples);
6539 tgl@sss.pgh.pa.us 498 : 148335 : pfree(state->readptrs);
6965 neilc@samurai.com 499 : 148335 : pfree(state);
9566 tgl@sss.pgh.pa.us 500 : 148335 : }
501 : :
502 : : /*
503 : : * tuplestore_select_read_pointer - make the specified read pointer active
504 : : */
505 : : void
6539 506 : 2924552 : tuplestore_select_read_pointer(Tuplestorestate *state, int ptr)
507 : : {
508 : : TSReadPointer *readptr;
509 : : TSReadPointer *oldptr;
510 : :
511 [ + - - + ]: 2924552 : Assert(ptr >= 0 && ptr < state->readptrcount);
512 : :
513 : : /* No work if already active */
514 [ + + ]: 2924552 : if (ptr == state->activeptr)
515 : 763945 : return;
516 : :
6533 517 : 2160607 : readptr = &state->readptrs[ptr];
518 : 2160607 : oldptr = &state->readptrs[state->activeptr];
519 : :
6539 520 [ + + - ]: 2160607 : switch (state->status)
521 : : {
522 : 2160599 : case TSS_INMEM:
523 : : case TSS_WRITEFILE:
524 : : /* no work */
525 : 2160599 : break;
526 : 8 : case TSS_READFILE:
527 : :
528 : : /*
529 : : * First, save the current read position in the pointer about to
530 : : * become inactive.
531 : : */
6533 532 [ + - ]: 8 : if (!oldptr->eof_reached)
533 : 8 : BufFileTell(state->myfile,
534 : : &oldptr->file,
535 : : &oldptr->offset);
536 : :
537 : : /*
538 : : * We have to make the temp file's seek position equal to the
539 : : * logical position of the new read pointer. In eof_reached
540 : : * state, that's the EOF, which we have available from the saved
541 : : * write position.
542 : : */
6539 543 [ - + ]: 8 : if (readptr->eof_reached)
544 : : {
6539 tgl@sss.pgh.pa.us 545 [ # # ]:UBC 0 : if (BufFileSeek(state->myfile,
546 : : state->writepos_file,
547 : : state->writepos_offset,
548 : : SEEK_SET) != 0)
4459 549 [ # # ]: 0 : ereport(ERROR,
550 : : (errcode_for_file_access(),
551 : : errmsg("could not seek in tuplestore temporary file")));
552 : : }
553 : : else
554 : : {
6539 tgl@sss.pgh.pa.us 555 [ - + ]:CBC 8 : if (BufFileSeek(state->myfile,
556 : : readptr->file,
557 : : readptr->offset,
558 : : SEEK_SET) != 0)
4459 tgl@sss.pgh.pa.us 559 [ # # ]:UBC 0 : ereport(ERROR,
560 : : (errcode_for_file_access(),
561 : : errmsg("could not seek in tuplestore temporary file")));
562 : : }
6539 tgl@sss.pgh.pa.us 563 :CBC 8 : break;
6539 tgl@sss.pgh.pa.us 564 :UBC 0 : default:
565 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
566 : : break;
567 : : }
568 : :
6539 tgl@sss.pgh.pa.us 569 :CBC 2160607 : state->activeptr = ptr;
570 : : }
571 : :
572 : : /*
573 : : * tuplestore_tuple_count
574 : : *
575 : : * Returns the number of tuples added since creation or the last
576 : : * tuplestore_clear().
577 : : */
578 : : int64
3436 kgrittn@postgresql.o 579 : 4466 : tuplestore_tuple_count(Tuplestorestate *state)
580 : : {
581 : 4466 : return state->tuples;
582 : : }
583 : :
584 : : /*
585 : : * tuplestore_ateof
586 : : *
587 : : * Returns the active read pointer's eof_reached state.
588 : : */
589 : : bool
8572 tgl@sss.pgh.pa.us 590 : 3255808 : tuplestore_ateof(Tuplestorestate *state)
591 : : {
6539 592 : 3255808 : return state->readptrs[state->activeptr].eof_reached;
593 : : }
594 : :
595 : : /*
596 : : * Grow the memtuples[] array, if possible within our memory constraint. We
597 : : * must not exceed INT_MAX tuples in memory or the caller-provided memory
598 : : * limit. Return true if we were able to enlarge the array, false if not.
599 : : *
600 : : * Normally, at each increment we double the size of the array. When doing
601 : : * that would exceed a limit, we attempt one last, smaller increase (and then
602 : : * clear the growmemtuples flag so we don't try any more). That allows us to
603 : : * use memory as fully as permitted; sticking to the pure doubling rule could
604 : : * result in almost half going unused. Because availMem moves around with
605 : : * tuple addition/removal, we need some rule to prevent making repeated small
606 : : * increases in memtupsize, which would just be useless thrashing. The
607 : : * growmemtuples flag accomplishes that and also prevents useless
608 : : * recalculations in this function.
609 : : */
610 : : static bool
4970 611 : 1356 : grow_memtuples(Tuplestorestate *state)
612 : : {
613 : : int newmemtupsize;
614 : 1356 : int memtupsize = state->memtupsize;
4802 noah@leadboat.com 615 : 1356 : int64 memNowUsed = state->allowedMem - state->availMem;
616 : :
617 : : /* Forget it if we've already maxed out memtuples, per comment above */
4970 tgl@sss.pgh.pa.us 618 [ + + ]: 1356 : if (!state->growmemtuples)
619 : 26 : return false;
620 : :
621 : : /* Select new value of memtupsize */
622 [ + + ]: 1330 : if (memNowUsed <= state->availMem)
623 : : {
624 : : /*
625 : : * We've used no more than half of allowedMem; double our usage,
626 : : * clamping at INT_MAX tuples.
627 : : */
4809 noah@leadboat.com 628 [ + - ]: 1281 : if (memtupsize < INT_MAX / 2)
629 : 1281 : newmemtupsize = memtupsize * 2;
630 : : else
631 : : {
4809 noah@leadboat.com 632 :UBC 0 : newmemtupsize = INT_MAX;
633 : 0 : state->growmemtuples = false;
634 : : }
635 : : }
636 : : else
637 : : {
638 : : /*
639 : : * This will be the last increment of memtupsize. Abandon doubling
640 : : * strategy and instead increase as much as we safely can.
641 : : *
642 : : * To stay within allowedMem, we can't increase memtupsize by more
643 : : * than availMem / sizeof(void *) elements. In practice, we want to
644 : : * increase it by considerably less, because we need to leave some
645 : : * space for the tuples to which the new array slots will refer. We
646 : : * assume the new tuples will be about the same size as the tuples
647 : : * we've already seen, and thus we can extrapolate from the space
648 : : * consumption so far to estimate an appropriate new size for the
649 : : * memtuples array. The optimal value might be higher or lower than
650 : : * this estimate, but it's hard to know that in advance. We again
651 : : * clamp at INT_MAX tuples.
652 : : *
653 : : * This calculation is safe against enlarging the array so much that
654 : : * LACKMEM becomes true, because the memory currently used includes
655 : : * the present array; thus, there would be enough allowedMem for the
656 : : * new array elements even if no other memory were currently used.
657 : : *
658 : : * We do the arithmetic in float8, because otherwise the product of
659 : : * memtupsize and allowedMem could overflow. Any inaccuracy in the
660 : : * result should be insignificant; but even if we computed a
661 : : * completely insane result, the checks below will prevent anything
662 : : * really bad from happening.
663 : : */
664 : : double grow_ratio;
665 : :
4970 tgl@sss.pgh.pa.us 666 :CBC 49 : grow_ratio = (double) state->allowedMem / (double) memNowUsed;
4809 noah@leadboat.com 667 [ + - ]: 49 : if (memtupsize * grow_ratio < INT_MAX)
668 : 49 : newmemtupsize = (int) (memtupsize * grow_ratio);
669 : : else
4809 noah@leadboat.com 670 :UBC 0 : newmemtupsize = INT_MAX;
671 : :
672 : : /* We won't make any further enlargement attempts */
4970 tgl@sss.pgh.pa.us 673 :CBC 49 : state->growmemtuples = false;
674 : : }
675 : :
676 : : /* Must enlarge array by at least one element, else report failure */
677 [ - + ]: 1330 : if (newmemtupsize <= memtupsize)
4970 tgl@sss.pgh.pa.us 678 :UBC 0 : goto noalloc;
679 : :
680 : : /*
681 : : * On a 32-bit machine, allowedMem could exceed MaxAllocHugeSize. Clamp
682 : : * to ensure our request won't be rejected. Note that we can easily
683 : : * exhaust address space before facing this outcome. (This is presently
684 : : * impossible due to guc.c's MAX_KILOBYTES limitation on work_mem, but
685 : : * don't rely on that at this distance.)
686 : : */
4809 noah@leadboat.com 687 [ - + ]:CBC 1330 : if ((Size) newmemtupsize >= MaxAllocHugeSize / sizeof(void *))
688 : : {
4809 noah@leadboat.com 689 :UBC 0 : newmemtupsize = (int) (MaxAllocHugeSize / sizeof(void *));
4970 tgl@sss.pgh.pa.us 690 : 0 : state->growmemtuples = false; /* can't grow any more */
691 : : }
692 : :
693 : : /*
694 : : * We need to be sure that we do not cause LACKMEM to become true, else
695 : : * the space management algorithm will go nuts. The code above should
696 : : * never generate a dangerous request, but to be safe, check explicitly
697 : : * that the array growth fits within availMem. (We could still cause
698 : : * LACKMEM if the memory chunk overhead associated with the memtuples
699 : : * array were to increase. That shouldn't happen because we chose the
700 : : * initial array size large enough to ensure that palloc will be treating
701 : : * both old and new arrays as separate chunks. But we'll check LACKMEM
702 : : * explicitly below just in case.)
703 : : */
4802 noah@leadboat.com 704 [ - + ]:CBC 1330 : if (state->availMem < (int64) ((newmemtupsize - memtupsize) * sizeof(void *)))
4970 tgl@sss.pgh.pa.us 705 :UBC 0 : goto noalloc;
706 : :
707 : : /* OK, do it */
4970 tgl@sss.pgh.pa.us 708 :CBC 1330 : FREEMEM(state, GetMemoryChunkSpace(state->memtuples));
709 : 1330 : state->memtuples = (void **)
4809 noah@leadboat.com 710 : 1330 : repalloc_huge(state->memtuples,
711 : : newmemtupsize * sizeof(void *));
150 tgl@sss.pgh.pa.us 712 : 1330 : state->memtupsize = newmemtupsize;
4970 713 : 1330 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
714 [ - + ]: 1330 : if (LACKMEM(state))
4041 tgl@sss.pgh.pa.us 715 [ # # ]:UBC 0 : elog(ERROR, "unexpected out-of-memory situation in tuplestore");
4970 tgl@sss.pgh.pa.us 716 :CBC 1330 : return true;
717 : :
4970 tgl@sss.pgh.pa.us 718 :UBC 0 : noalloc:
719 : : /* If for any reason we didn't realloc, shut off future attempts */
720 : 0 : state->growmemtuples = false;
721 : 0 : return false;
722 : : }
723 : :
724 : : /*
725 : : * Accept one tuple and append it to the tuplestore.
726 : : *
727 : : * Note that the input tuple is always copied; the caller need not save it.
728 : : *
729 : : * If the active read pointer is currently "at EOF", it remains so (the read
730 : : * pointer implicitly advances along with the write pointer); otherwise the
731 : : * read pointer is unchanged. Non-active read pointers do not move, which
732 : : * means they are certain to not be "at EOF" immediately after puttuple.
733 : : * This curious-seeming behavior is for the convenience of nodeMaterial.c and
734 : : * nodeCtescan.c, which would otherwise need to do extra pointer repositioning
735 : : * steps.
736 : : *
737 : : * tuplestore_puttupleslot() is a convenience routine to collect data from
738 : : * a TupleTableSlot without an extra copy operation.
739 : : */
740 : : void
7366 tgl@sss.pgh.pa.us 741 :CBC 1415443 : tuplestore_puttupleslot(Tuplestorestate *state,
742 : : TupleTableSlot *slot)
743 : : {
744 : : MinimalTuple tuple;
6085 heikki.linnakangas@i 745 : 1415443 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
746 : :
747 : : /*
748 : : * Form a MinimalTuple in working memory
749 : : */
7366 tgl@sss.pgh.pa.us 750 : 1415443 : tuple = ExecCopySlotMinimalTuple(slot);
751 : 1415443 : USEMEM(state, GetMemoryChunkSpace(tuple));
752 : :
637 peter@eisentraut.org 753 : 1415443 : tuplestore_puttuple_common(state, tuple);
754 : :
6085 heikki.linnakangas@i 755 : 1415443 : MemoryContextSwitchTo(oldcxt);
7366 tgl@sss.pgh.pa.us 756 : 1415443 : }
757 : :
758 : : /*
759 : : * "Standard" case to copy from a HeapTuple. This is actually now somewhat
760 : : * deprecated, but not worth getting rid of in view of the number of callers.
761 : : */
762 : : void
763 : 1147430 : tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
764 : : {
6085 heikki.linnakangas@i 765 : 1147430 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
766 : :
767 : : /*
768 : : * Copy the tuple. (Must do this even in WRITEFILE case. Note that
769 : : * COPYTUP includes USEMEM, so we needn't do that here.)
770 : : */
9566 tgl@sss.pgh.pa.us 771 : 1147430 : tuple = COPYTUP(state, tuple);
772 : :
637 peter@eisentraut.org 773 : 1147430 : tuplestore_puttuple_common(state, tuple);
774 : :
6085 heikki.linnakangas@i 775 : 1147430 : MemoryContextSwitchTo(oldcxt);
7366 tgl@sss.pgh.pa.us 776 : 1147430 : }
777 : :
778 : : /*
779 : : * Similar to tuplestore_puttuple(), but work from values + nulls arrays.
780 : : * This avoids an extra tuple-construction operation.
781 : : */
782 : : void
6729 neilc@samurai.com 783 : 10065729 : tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc,
784 : : const Datum *values, const bool *isnull)
785 : : {
786 : : MinimalTuple tuple;
6085 heikki.linnakangas@i 787 : 10065729 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
788 : :
521 jdavis@postgresql.or 789 : 10065729 : tuple = heap_form_minimal_tuple(tdesc, values, isnull, 0);
5552 tgl@sss.pgh.pa.us 790 : 10065729 : USEMEM(state, GetMemoryChunkSpace(tuple));
791 : :
637 peter@eisentraut.org 792 : 10065729 : tuplestore_puttuple_common(state, tuple);
793 : :
6026 bruce@momjian.us 794 : 10065729 : MemoryContextSwitchTo(oldcxt);
6729 neilc@samurai.com 795 : 10065729 : }
796 : :
797 : : static void
7366 tgl@sss.pgh.pa.us 798 : 12628602 : tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
799 : : {
800 : : TSReadPointer *readptr;
801 : : int i;
802 : : ResourceOwner oldowner;
803 : : MemoryContext oldcxt;
804 : :
3436 kgrittn@postgresql.o 805 : 12628602 : state->tuples++;
806 : :
9566 tgl@sss.pgh.pa.us 807 [ + + + - ]: 12628602 : switch (state->status)
808 : : {
8572 809 : 10444505 : case TSS_INMEM:
810 : :
811 : : /*
812 : : * Update read pointers as needed; see API spec above.
813 : : */
6536 814 : 10444505 : readptr = state->readptrs;
815 [ + + ]: 22560834 : for (i = 0; i < state->readptrcount; readptr++, i++)
816 : : {
817 [ + + + + ]: 12116329 : if (readptr->eof_reached && i != state->activeptr)
818 : : {
819 : 318 : readptr->eof_reached = false;
820 : 318 : readptr->current = state->memtupcount;
821 : : }
822 : : }
823 : :
824 : : /*
825 : : * Grow the array as needed. Note that we try to grow the array
826 : : * when there is still one free slot remaining --- if we fail,
827 : : * there'll still be room to store the incoming tuple, and then
828 : : * we'll switch to tape-based operation.
829 : : */
7481 830 [ + + ]: 10444505 : if (state->memtupcount >= state->memtupsize - 1)
831 : : {
4970 832 : 1356 : (void) grow_memtuples(state);
833 [ - + ]: 1356 : Assert(state->memtupcount < state->memtupsize);
834 : : }
835 : :
836 : : /* Stash the tuple in the in-memory array */
9566 837 : 10444505 : state->memtuples[state->memtupcount++] = tuple;
838 : :
839 : : /*
840 : : * Done if we still fit in available memory and have array slots.
841 : : */
7481 842 [ + + + + ]: 10444505 : if (state->memtupcount < state->memtupsize && !LACKMEM(state))
9566 843 : 10444412 : return;
844 : :
845 : : /*
846 : : * Nope; time to switch to tape-based operation. Make sure that
847 : : * the temp file(s) are created in suitable temp tablespaces.
848 : : */
7021 849 : 93 : PrepareTempTablespaces();
850 : :
851 : : /* associate the file with the store's resource owner */
6085 heikki.linnakangas@i 852 : 93 : oldowner = CurrentResourceOwner;
853 : 93 : CurrentResourceOwner = state->resowner;
854 : :
855 : : /*
856 : : * We switch out of the state->context as this is a generation
857 : : * context, which isn't ideal for allocations relating to the
858 : : * BufFile.
859 : : */
782 drowley@postgresql.o 860 : 93 : oldcxt = MemoryContextSwitchTo(state->context->parent);
861 : :
7021 tgl@sss.pgh.pa.us 862 : 93 : state->myfile = BufFileCreateTemp(state->interXact);
863 : :
782 drowley@postgresql.o 864 : 93 : MemoryContextSwitchTo(oldcxt);
865 : :
6085 heikki.linnakangas@i 866 : 93 : CurrentResourceOwner = oldowner;
867 : :
868 : : /*
869 : : * Freeze the decision about whether trailing length words will be
870 : : * used. We can't change this choice once data is on tape, even
871 : : * though callers might drop the requirement.
872 : : */
6539 tgl@sss.pgh.pa.us 873 : 93 : state->backward = (state->eflags & EXEC_FLAG_BACKWARD) != 0;
874 : :
875 : : /*
876 : : * Update the maximum space used before dumping the tuples. It's
877 : : * possible that more space will be used by the tuples in memory
878 : : * than the space that will be used on disk.
879 : : */
714 drowley@postgresql.o 880 : 93 : tuplestore_updatemax(state);
881 : :
9566 tgl@sss.pgh.pa.us 882 : 93 : state->status = TSS_WRITEFILE;
883 : 93 : dumptuples(state);
884 : 93 : break;
885 : 2184089 : case TSS_WRITEFILE:
886 : :
887 : : /*
888 : : * Update read pointers as needed; see API spec above. Note:
889 : : * BufFileTell is quite cheap, so not worth trying to avoid
890 : : * multiple calls.
891 : : */
6536 892 : 2184089 : readptr = state->readptrs;
893 [ + + ]: 4379150 : for (i = 0; i < state->readptrcount; readptr++, i++)
894 : : {
895 [ - + - - ]: 2195061 : if (readptr->eof_reached && i != state->activeptr)
896 : : {
6536 tgl@sss.pgh.pa.us 897 :UBC 0 : readptr->eof_reached = false;
898 : 0 : BufFileTell(state->myfile,
899 : : &readptr->file,
900 : : &readptr->offset);
901 : : }
902 : : }
903 : :
9566 tgl@sss.pgh.pa.us 904 :CBC 2184089 : WRITETUP(state, tuple);
905 : 2184089 : break;
8572 906 : 8 : case TSS_READFILE:
907 : :
908 : : /*
909 : : * Switch from reading to writing.
910 : : */
6539 911 [ + - ]: 8 : if (!state->readptrs[state->activeptr].eof_reached)
8572 912 : 8 : BufFileTell(state->myfile,
6539 913 : 8 : &state->readptrs[state->activeptr].file,
914 : 8 : &state->readptrs[state->activeptr].offset);
8572 915 [ - + ]: 8 : if (BufFileSeek(state->myfile,
916 : : state->writepos_file, state->writepos_offset,
917 : : SEEK_SET) != 0)
4459 tgl@sss.pgh.pa.us 918 [ # # ]:UBC 0 : ereport(ERROR,
919 : : (errcode_for_file_access(),
920 : : errmsg("could not seek in tuplestore temporary file")));
8572 tgl@sss.pgh.pa.us 921 :CBC 8 : state->status = TSS_WRITEFILE;
922 : :
923 : : /*
924 : : * Update read pointers as needed; see API spec above.
925 : : */
6536 926 : 8 : readptr = state->readptrs;
927 [ + + ]: 24 : for (i = 0; i < state->readptrcount; readptr++, i++)
928 : : {
929 [ - + - - ]: 16 : if (readptr->eof_reached && i != state->activeptr)
930 : : {
6536 tgl@sss.pgh.pa.us 931 :UBC 0 : readptr->eof_reached = false;
932 : 0 : readptr->file = state->writepos_file;
933 : 0 : readptr->offset = state->writepos_offset;
934 : : }
935 : : }
936 : :
8572 tgl@sss.pgh.pa.us 937 :CBC 8 : WRITETUP(state, tuple);
9566 938 : 8 : break;
9566 tgl@sss.pgh.pa.us 939 :UBC 0 : default:
8434 940 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
941 : : break;
942 : : }
943 : : }
944 : :
945 : : /*
946 : : * Fetch the next tuple in either forward or back direction.
947 : : * Returns NULL if no more tuples. If should_free is set, the
948 : : * caller must pfree the returned tuple when done with it.
949 : : *
950 : : * Backward scan is only allowed if randomAccess was set true or
951 : : * EXEC_FLAG_BACKWARD was specified to tuplestore_set_eflags().
952 : : */
953 : : static void *
9566 tgl@sss.pgh.pa.us 954 :CBC 16267405 : tuplestore_gettuple(Tuplestorestate *state, bool forward,
955 : : bool *should_free)
956 : : {
6539 957 : 16267405 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
958 : : unsigned int tuplen;
959 : : void *tup;
960 : :
961 [ + + - + ]: 16267405 : Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD));
962 : :
9566 963 [ + + + - ]: 16267405 : switch (state->status)
964 : : {
8572 965 : 11826640 : case TSS_INMEM:
9566 966 : 11826640 : *should_free = false;
967 [ + + ]: 11826640 : if (forward)
968 : : {
6539 969 [ + + ]: 11709113 : if (readptr->eof_reached)
970 : 128 : return NULL;
971 [ + + ]: 11708985 : if (readptr->current < state->memtupcount)
972 : : {
973 : : /* We have another tuple, so return it */
974 : 11466841 : return state->memtuples[readptr->current++];
975 : : }
976 : 242144 : readptr->eof_reached = true;
9566 977 : 242144 : return NULL;
978 : : }
979 : : else
980 : : {
981 : : /*
982 : : * if all tuples are fetched already then we return last
983 : : * tuple, else tuple before last returned.
984 : : */
6539 985 [ + + ]: 117527 : if (readptr->eof_reached)
986 : : {
987 : 1909 : readptr->current = state->memtupcount;
988 : 1909 : readptr->eof_reached = false;
989 : : }
990 : : else
991 : : {
5739 992 [ - + ]: 115618 : if (readptr->current <= state->memtupdeleted)
993 : : {
6452 tgl@sss.pgh.pa.us 994 [ # # ]:UBC 0 : Assert(!state->truncated);
9566 995 : 0 : return NULL;
996 : : }
6286 bruce@momjian.us 997 :CBC 115618 : readptr->current--; /* last returned tuple */
998 : : }
5739 tgl@sss.pgh.pa.us 999 [ + + ]: 117527 : if (readptr->current <= state->memtupdeleted)
1000 : : {
6452 1001 [ - + ]: 21 : Assert(!state->truncated);
6539 1002 : 21 : return NULL;
1003 : : }
1004 : 117506 : return state->memtuples[readptr->current - 1];
1005 : : }
1006 : : break;
1007 : :
8572 1008 : 101 : case TSS_WRITEFILE:
1009 : : /* Skip state change if we'll just return NULL */
6539 1010 [ - + - - ]: 101 : if (readptr->eof_reached && forward)
8572 tgl@sss.pgh.pa.us 1011 :UBC 0 : return NULL;
1012 : :
1013 : : /*
1014 : : * Switch from writing to reading.
1015 : : */
8572 tgl@sss.pgh.pa.us 1016 :CBC 101 : BufFileTell(state->myfile,
1017 : : &state->writepos_file, &state->writepos_offset);
6539 1018 [ + - ]: 101 : if (!readptr->eof_reached)
8572 1019 [ - + ]: 101 : if (BufFileSeek(state->myfile,
1020 : : readptr->file, readptr->offset,
1021 : : SEEK_SET) != 0)
4459 tgl@sss.pgh.pa.us 1022 [ # # ]:UBC 0 : ereport(ERROR,
1023 : : (errcode_for_file_access(),
1024 : : errmsg("could not seek in tuplestore temporary file")));
8572 tgl@sss.pgh.pa.us 1025 :CBC 101 : state->status = TSS_READFILE;
1026 : : pg_fallthrough;
1027 : :
9566 1028 : 4440765 : case TSS_READFILE:
1029 : 4440765 : *should_free = true;
1030 [ + - ]: 4440765 : if (forward)
1031 : : {
1032 [ + + ]: 4440765 : if ((tuplen = getlen(state, true)) != 0)
1033 : : {
1034 : 4440671 : tup = READTUP(state, tuplen);
1035 : 4440671 : return tup;
1036 : : }
1037 : : else
1038 : : {
6539 1039 : 94 : readptr->eof_reached = true;
9566 1040 : 94 : return NULL;
1041 : : }
1042 : : }
1043 : :
1044 : : /*
1045 : : * Backward.
1046 : : *
1047 : : * if all tuples are fetched already then we return last tuple,
1048 : : * else tuple before last returned.
1049 : : *
1050 : : * Back up to fetch previously-returned tuple's ending length
1051 : : * word. If seek fails, assume we are at start of file.
1052 : : */
247 michael@paquier.xyz 1053 [ # # ]:UBC 0 : if (BufFileSeek(state->myfile, 0, -(pgoff_t) sizeof(unsigned int),
1054 : : SEEK_CUR) != 0)
1055 : : {
1056 : : /* even a failed backwards fetch gets you out of eof state */
6539 tgl@sss.pgh.pa.us 1057 : 0 : readptr->eof_reached = false;
6452 1058 [ # # ]: 0 : Assert(!state->truncated);
8572 1059 : 0 : return NULL;
1060 : : }
1061 : 0 : tuplen = getlen(state, false);
1062 : :
6539 1063 [ # # ]: 0 : if (readptr->eof_reached)
1064 : : {
1065 : 0 : readptr->eof_reached = false;
1066 : : /* We will return the tuple returned before returning NULL */
1067 : : }
1068 : : else
1069 : : {
1070 : : /*
1071 : : * Back up to get ending length word of tuple before it.
1072 : : */
9566 1073 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0,
247 michael@paquier.xyz 1074 : 0 : -(pgoff_t) (tuplen + 2 * sizeof(unsigned int)),
1075 : : SEEK_CUR) != 0)
1076 : : {
1077 : : /*
1078 : : * If that fails, presumably the prev tuple is the first
1079 : : * in the file. Back up so that it becomes next to read
1080 : : * in forward direction (not obviously right, but that is
1081 : : * what in-memory case does).
1082 : : */
9566 tgl@sss.pgh.pa.us 1083 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0,
247 michael@paquier.xyz 1084 : 0 : -(pgoff_t) (tuplen + sizeof(unsigned int)),
1085 : : SEEK_CUR) != 0)
4459 tgl@sss.pgh.pa.us 1086 [ # # ]: 0 : ereport(ERROR,
1087 : : (errcode_for_file_access(),
1088 : : errmsg("could not seek in tuplestore temporary file")));
6452 1089 [ # # ]: 0 : Assert(!state->truncated);
9566 1090 : 0 : return NULL;
1091 : : }
8572 1092 : 0 : tuplen = getlen(state, false);
1093 : : }
1094 : :
1095 : : /*
1096 : : * Now we have the length of the prior tuple, back up and read it.
1097 : : * Note: READTUP expects we are positioned after the initial
1098 : : * length word of the tuple, so back up to that point.
1099 : : */
9566 1100 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0,
247 michael@paquier.xyz 1101 : 0 : -(pgoff_t) tuplen,
1102 : : SEEK_CUR) != 0)
4459 tgl@sss.pgh.pa.us 1103 [ # # ]: 0 : ereport(ERROR,
1104 : : (errcode_for_file_access(),
1105 : : errmsg("could not seek in tuplestore temporary file")));
9566 1106 : 0 : tup = READTUP(state, tuplen);
1107 : 0 : return tup;
1108 : :
1109 : 0 : default:
8434 1110 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1111 : : return NULL; /* keep compiler quiet */
1112 : : }
1113 : : }
1114 : :
1115 : : /*
1116 : : * tuplestore_gettupleslot - exported function to fetch a MinimalTuple
1117 : : *
1118 : : * If successful, put tuple in slot and return true; else, clear the slot
1119 : : * and return false.
1120 : : *
1121 : : * If copy is true, the slot receives a copied tuple (allocated in current
1122 : : * memory context) that will stay valid regardless of future manipulations of
1123 : : * the tuplestore's state. If copy is false, the slot may just receive a
1124 : : * pointer to a tuple held within the tuplestore. The latter is more
1125 : : * efficient but the slot contents may be corrupted if additional writes to
1126 : : * the tuplestore occur. (If using tuplestore_trim, see comments therein.)
1127 : : */
1128 : : bool
7366 tgl@sss.pgh.pa.us 1129 :CBC 16151865 : tuplestore_gettupleslot(Tuplestorestate *state, bool forward,
1130 : : bool copy, TupleTableSlot *slot)
1131 : : {
1132 : : MinimalTuple tuple;
1133 : : bool should_free;
1134 : :
1135 : 16151865 : tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free);
1136 : :
1137 [ + + ]: 16151865 : if (tuple)
1138 : : {
6362 1139 [ + + + + ]: 15911433 : if (copy && !should_free)
1140 : : {
521 jdavis@postgresql.or 1141 : 1326052 : tuple = heap_copy_minimal_tuple(tuple, 0);
6362 tgl@sss.pgh.pa.us 1142 : 1326052 : should_free = true;
1143 : : }
7366 1144 : 15911433 : ExecStoreMinimalTuple(tuple, slot, should_free);
1145 : 15911433 : return true;
1146 : : }
1147 : : else
1148 : : {
1149 : 240432 : ExecClearTuple(slot);
1150 : 240432 : return false;
1151 : : }
1152 : : }
1153 : :
1154 : : /*
1155 : : * tuplestore_gettupleslot_force - exported function to fetch a tuple
1156 : : *
1157 : : * This is identical to tuplestore_gettupleslot except the given slot can be
1158 : : * any kind of slot; it need not be one that will accept a MinimalTuple.
1159 : : */
1160 : : bool
161 1161 : 219 : tuplestore_gettupleslot_force(Tuplestorestate *state, bool forward,
1162 : : bool copy, TupleTableSlot *slot)
1163 : : {
1164 : : MinimalTuple tuple;
1165 : : bool should_free;
1166 : :
1167 : 219 : tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free);
1168 : :
1169 [ + + ]: 219 : if (tuple)
1170 : : {
1171 [ - + - - ]: 133 : if (copy && !should_free)
1172 : : {
161 tgl@sss.pgh.pa.us 1173 :UBC 0 : tuple = heap_copy_minimal_tuple(tuple, 0);
1174 : 0 : should_free = true;
1175 : : }
161 tgl@sss.pgh.pa.us 1176 :CBC 133 : ExecForceStoreMinimalTuple(tuple, slot, should_free);
1177 : 133 : return true;
1178 : : }
1179 : : else
1180 : : {
1181 : 86 : ExecClearTuple(slot);
1182 : 86 : return false;
1183 : : }
1184 : : }
1185 : :
1186 : : /*
1187 : : * tuplestore_advance - exported function to adjust position without fetching
1188 : : *
1189 : : * We could optimize this case to avoid palloc/pfree overhead, but for the
1190 : : * moment it doesn't seem worthwhile.
1191 : : */
1192 : : bool
7366 1193 : 115321 : tuplestore_advance(Tuplestorestate *state, bool forward)
1194 : : {
1195 : : void *tuple;
1196 : : bool should_free;
1197 : :
1198 : 115321 : tuple = tuplestore_gettuple(state, forward, &should_free);
1199 : :
1200 [ + + ]: 115321 : if (tuple)
1201 : : {
1202 [ - + ]: 113452 : if (should_free)
7366 tgl@sss.pgh.pa.us 1203 :UBC 0 : pfree(tuple);
7366 tgl@sss.pgh.pa.us 1204 :CBC 113452 : return true;
1205 : : }
1206 : : else
1207 : : {
1208 : 1869 : return false;
1209 : : }
1210 : : }
1211 : :
1212 : : /*
1213 : : * Advance over N tuples in either forward or back direction,
1214 : : * without returning any data. N<=0 is a no-op.
1215 : : * Returns true if successful, false if ran out of tuples.
1216 : : */
1217 : : bool
4519 1218 : 891321 : tuplestore_skiptuples(Tuplestorestate *state, int64 ntuples, bool forward)
1219 : : {
1220 : 891321 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
1221 : :
1222 [ + + - + ]: 891321 : Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD));
1223 : :
1224 [ + + ]: 891321 : if (ntuples <= 0)
1225 : 12 : return true;
1226 : :
1227 [ + - ]: 891309 : switch (state->status)
1228 : : {
1229 : 891309 : case TSS_INMEM:
1230 [ + + ]: 891309 : if (forward)
1231 : : {
1232 [ - + ]: 889439 : if (readptr->eof_reached)
4519 tgl@sss.pgh.pa.us 1233 :UBC 0 : return false;
4519 tgl@sss.pgh.pa.us 1234 [ + + ]:CBC 889439 : if (state->memtupcount - readptr->current >= ntuples)
1235 : : {
1236 : 889362 : readptr->current += ntuples;
1237 : 889362 : return true;
1238 : : }
1239 : 77 : readptr->current = state->memtupcount;
1240 : 77 : readptr->eof_reached = true;
1241 : 77 : return false;
1242 : : }
1243 : : else
1244 : : {
1245 [ - + ]: 1870 : if (readptr->eof_reached)
1246 : : {
4519 tgl@sss.pgh.pa.us 1247 :UBC 0 : readptr->current = state->memtupcount;
1248 : 0 : readptr->eof_reached = false;
1249 : 0 : ntuples--;
1250 : : }
4519 tgl@sss.pgh.pa.us 1251 [ + - ]:CBC 1870 : if (readptr->current - state->memtupdeleted > ntuples)
1252 : : {
1253 : 1870 : readptr->current -= ntuples;
1254 : 1870 : return true;
1255 : : }
4519 tgl@sss.pgh.pa.us 1256 [ # # ]:UBC 0 : Assert(!state->truncated);
1257 : 0 : readptr->current = state->memtupdeleted;
1258 : 0 : return false;
1259 : : }
1260 : : break;
1261 : :
1262 : 0 : default:
1263 : : /* We don't currently try hard to optimize other cases */
1264 [ # # ]: 0 : while (ntuples-- > 0)
1265 : : {
1266 : : void *tuple;
1267 : : bool should_free;
1268 : :
1269 : 0 : tuple = tuplestore_gettuple(state, forward, &should_free);
1270 : :
1271 [ # # ]: 0 : if (tuple == NULL)
1272 : 0 : return false;
1273 [ # # ]: 0 : if (should_free)
1274 : 0 : pfree(tuple);
1275 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1276 : : }
1277 : 0 : return true;
1278 : : }
1279 : : }
1280 : :
1281 : : /*
1282 : : * dumptuples - remove tuples from memory and write to tape
1283 : : *
1284 : : * As a side effect, we must convert each read pointer's position from
1285 : : * "current" to file/offset format. But eof_reached pointers don't
1286 : : * need to change state.
1287 : : */
1288 : : static void
9566 tgl@sss.pgh.pa.us 1289 :CBC 93 : dumptuples(Tuplestorestate *state)
1290 : : {
1291 : : int i;
1292 : :
5739 1293 : 93 : for (i = state->memtupdeleted;; i++)
8572 1294 : 2285590 : {
6539 1295 : 2285683 : TSReadPointer *readptr = state->readptrs;
1296 : : int j;
1297 : :
1298 [ + + ]: 4580386 : for (j = 0; j < state->readptrcount; readptr++, j++)
1299 : : {
1300 [ + + + - ]: 2294703 : if (i == readptr->current && !readptr->eof_reached)
1301 : 101 : BufFileTell(state->myfile,
1302 : : &readptr->file, &readptr->offset);
1303 : : }
8572 1304 [ + + ]: 2285683 : if (i >= state->memtupcount)
1305 : 93 : break;
9566 1306 : 2285590 : WRITETUP(state, state->memtuples[i]);
1307 : :
1308 : : /*
1309 : : * Increase memtupdeleted to track the fact that we just deleted that
1310 : : * tuple. Think not to remove this on the grounds that we'll reset
1311 : : * memtupdeleted to zero below. We might not reach that if some later
1312 : : * WRITETUP fails (e.g. due to overrunning temp_file_limit). If so,
1313 : : * we'd error out leaving an effectively-corrupt tuplestore, which
1314 : : * would be quite bad if it's a persistent data structure such as a
1315 : : * Portal's holdStore.
1316 : : */
150 1317 : 2285590 : state->memtupdeleted++;
1318 : : }
1319 : : /* Now we can reset memtupdeleted along with memtupcount */
5739 1320 : 93 : state->memtupdeleted = 0;
9566 1321 : 93 : state->memtupcount = 0;
1322 : 93 : }
1323 : :
1324 : : /*
1325 : : * tuplestore_rescan - rewind the active read pointer to start
1326 : : */
1327 : : void
1328 : 194481 : tuplestore_rescan(Tuplestorestate *state)
1329 : : {
6539 1330 : 194481 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
1331 : :
1332 [ - + ]: 194481 : Assert(readptr->eflags & EXEC_FLAG_REWIND);
6452 1333 [ - + ]: 194481 : Assert(!state->truncated);
1334 : :
9566 1335 [ + + - - ]: 194481 : switch (state->status)
1336 : : {
8572 1337 : 194404 : case TSS_INMEM:
6539 1338 : 194404 : readptr->eof_reached = false;
1339 : 194404 : readptr->current = 0;
8572 1340 : 194404 : break;
1341 : 77 : case TSS_WRITEFILE:
6539 1342 : 77 : readptr->eof_reached = false;
1343 : 77 : readptr->file = 0;
1247 peter@eisentraut.org 1344 : 77 : readptr->offset = 0;
9566 tgl@sss.pgh.pa.us 1345 : 77 : break;
9566 tgl@sss.pgh.pa.us 1346 :UBC 0 : case TSS_READFILE:
6539 1347 : 0 : readptr->eof_reached = false;
1247 peter@eisentraut.org 1348 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0, 0, SEEK_SET) != 0)
4459 tgl@sss.pgh.pa.us 1349 [ # # ]: 0 : ereport(ERROR,
1350 : : (errcode_for_file_access(),
1351 : : errmsg("could not seek in tuplestore temporary file")));
9566 1352 : 0 : break;
1353 : 0 : default:
8434 1354 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1355 : : break;
1356 : : }
9566 tgl@sss.pgh.pa.us 1357 :CBC 194481 : }
1358 : :
1359 : : /*
1360 : : * tuplestore_copy_read_pointer - copy a read pointer's state to another
1361 : : */
1362 : : void
6539 1363 : 40461 : tuplestore_copy_read_pointer(Tuplestorestate *state,
1364 : : int srcptr, int destptr)
1365 : : {
1366 : 40461 : TSReadPointer *sptr = &state->readptrs[srcptr];
1367 : 40461 : TSReadPointer *dptr = &state->readptrs[destptr];
1368 : :
1369 [ + - - + ]: 40461 : Assert(srcptr >= 0 && srcptr < state->readptrcount);
1370 [ + - - + ]: 40461 : Assert(destptr >= 0 && destptr < state->readptrcount);
1371 : :
1372 : : /* Assigning to self is a no-op */
1373 [ - + ]: 40461 : if (srcptr == destptr)
6539 tgl@sss.pgh.pa.us 1374 :UBC 0 : return;
1375 : :
6539 tgl@sss.pgh.pa.us 1376 [ - + ]:CBC 40461 : if (dptr->eflags != sptr->eflags)
1377 : : {
1378 : : /* Possible change of overall eflags, so copy and then recompute */
1379 : : int eflags;
1380 : : int i;
1381 : :
6539 tgl@sss.pgh.pa.us 1382 :UBC 0 : *dptr = *sptr;
1383 : 0 : eflags = state->readptrs[0].eflags;
1384 [ # # ]: 0 : for (i = 1; i < state->readptrcount; i++)
1385 : 0 : eflags |= state->readptrs[i].eflags;
1386 : 0 : state->eflags = eflags;
1387 : : }
1388 : : else
6539 tgl@sss.pgh.pa.us 1389 :CBC 40461 : *dptr = *sptr;
1390 : :
9566 1391 [ + - - ]: 40461 : switch (state->status)
1392 : : {
8572 1393 : 40461 : case TSS_INMEM:
1394 : : case TSS_WRITEFILE:
1395 : : /* no work */
9566 1396 : 40461 : break;
9566 tgl@sss.pgh.pa.us 1397 :UBC 0 : case TSS_READFILE:
1398 : :
1399 : : /*
1400 : : * This case is a bit tricky since the active read pointer's
1401 : : * position corresponds to the seek point, not what is in its
1402 : : * variables. Assigning to the active requires a seek, and
1403 : : * assigning from the active requires a tell, except when
1404 : : * eof_reached.
1405 : : */
6539 1406 [ # # ]: 0 : if (destptr == state->activeptr)
1407 : : {
1408 [ # # ]: 0 : if (dptr->eof_reached)
1409 : : {
1410 [ # # ]: 0 : if (BufFileSeek(state->myfile,
1411 : : state->writepos_file,
1412 : : state->writepos_offset,
1413 : : SEEK_SET) != 0)
4459 1414 [ # # ]: 0 : ereport(ERROR,
1415 : : (errcode_for_file_access(),
1416 : : errmsg("could not seek in tuplestore temporary file")));
1417 : : }
1418 : : else
1419 : : {
6539 1420 [ # # ]: 0 : if (BufFileSeek(state->myfile,
1421 : : dptr->file, dptr->offset,
1422 : : SEEK_SET) != 0)
4459 1423 [ # # ]: 0 : ereport(ERROR,
1424 : : (errcode_for_file_access(),
1425 : : errmsg("could not seek in tuplestore temporary file")));
1426 : : }
1427 : : }
6539 1428 [ # # ]: 0 : else if (srcptr == state->activeptr)
1429 : : {
1430 [ # # ]: 0 : if (!dptr->eof_reached)
1431 : 0 : BufFileTell(state->myfile,
1432 : : &dptr->file,
1433 : : &dptr->offset);
1434 : : }
9566 1435 : 0 : break;
1436 : 0 : default:
8434 1437 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1438 : : break;
1439 : : }
1440 : : }
1441 : :
1442 : : /*
1443 : : * tuplestore_trim - remove all no-longer-needed tuples
1444 : : *
1445 : : * Calling this function authorizes the tuplestore to delete all tuples
1446 : : * before the oldest read pointer, if no read pointer is marked as requiring
1447 : : * REWIND capability.
1448 : : *
1449 : : * Note: this is obviously safe if no pointer has BACKWARD capability either.
1450 : : * If a pointer is marked as BACKWARD but not REWIND capable, it means that
1451 : : * the pointer can be moved backward but not before the oldest other read
1452 : : * pointer.
1453 : : */
1454 : : void
6539 tgl@sss.pgh.pa.us 1455 :CBC 608010 : tuplestore_trim(Tuplestorestate *state)
1456 : : {
1457 : : int oldest;
1458 : : int nremove;
1459 : : int i;
1460 : :
1461 : : /*
1462 : : * Truncation is disallowed if any read pointer requires rewind
1463 : : * capability.
1464 : : */
6452 1465 [ - + ]: 608010 : if (state->eflags & EXEC_FLAG_REWIND)
6539 tgl@sss.pgh.pa.us 1466 :UBC 0 : return;
1467 : :
1468 : : /*
1469 : : * We don't bother trimming temp files since it usually would mean more
1470 : : * work than just letting them sit in kernel buffers until they age out.
1471 : : */
7038 tgl@sss.pgh.pa.us 1472 [ + + ]:CBC 608010 : if (state->status != TSS_INMEM)
1473 : 19992 : return;
1474 : :
1475 : : /* Find the oldest read pointer */
6539 1476 : 588018 : oldest = state->memtupcount;
1477 [ + + ]: 2561181 : for (i = 0; i < state->readptrcount; i++)
1478 : : {
1479 [ + + ]: 1973163 : if (!state->readptrs[i].eof_reached)
1480 : 1953407 : oldest = Min(oldest, state->readptrs[i].current);
1481 : : }
1482 : :
1483 : : /*
1484 : : * Note: you might think we could remove all the tuples before the oldest
1485 : : * "current", since that one is the next to be returned. However, since
1486 : : * tuplestore_gettuple returns a direct pointer to our internal copy of
1487 : : * the tuple, it's likely that the caller has still got the tuple just
1488 : : * before "current" referenced in a slot. So we keep one extra tuple
1489 : : * before the oldest "current". (Strictly speaking, we could require such
1490 : : * callers to use the "copy" flag to tuplestore_gettupleslot, but for
1491 : : * efficiency we allow this one case to not use "copy".)
1492 : : */
1493 : 588018 : nremove = oldest - 1;
7038 1494 [ + + ]: 588018 : if (nremove <= 0)
1495 : 5361 : return; /* nothing to do */
1496 : :
5739 1497 [ - + ]: 582657 : Assert(nremove >= state->memtupdeleted);
7038 1498 [ - + ]: 582657 : Assert(nremove <= state->memtupcount);
1499 : :
1500 : : /* before freeing any memory, update the statistics */
783 drowley@postgresql.o 1501 : 582657 : tuplestore_updatemax(state);
1502 : :
1503 : : /* Release no-longer-needed tuples */
5739 tgl@sss.pgh.pa.us 1504 [ + + ]: 1166010 : for (i = state->memtupdeleted; i < nremove; i++)
1505 : : {
7038 1506 : 583353 : FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i]));
1507 : 583353 : pfree(state->memtuples[i]);
5739 1508 : 583353 : state->memtuples[i] = NULL;
1509 : : /* As in dumptuples(), increment memtupdeleted synchronously */
150 1510 : 583353 : state->memtupdeleted++;
1511 : : }
1512 [ - + ]: 582657 : Assert(state->memtupdeleted == nremove);
1513 : :
1514 : : /* mark tuplestore as truncated (used for Assert crosschecks only) */
5739 1515 : 582657 : state->truncated = true;
1516 : :
1517 : : /*
1518 : : * If nremove is less than 1/8th memtupcount, just stop here, leaving the
1519 : : * "deleted" slots as NULL. This prevents us from expending O(N^2) time
1520 : : * repeatedly memmove-ing a large pointer array. The worst case space
1521 : : * wastage is pretty small, since it's just pointers and not whole tuples.
1522 : : */
1523 [ + + ]: 582657 : if (nremove < state->memtupcount / 8)
1524 : 75120 : return;
1525 : :
1526 : : /*
1527 : : * Slide the array down and readjust pointers.
1528 : : *
1529 : : * In mergejoin's current usage, it's demonstrable that there will always
1530 : : * be exactly one non-removed tuple; so optimize that case.
1531 : : */
7038 1532 [ + + ]: 507537 : if (nremove + 1 == state->memtupcount)
1533 : 420256 : state->memtuples[0] = state->memtuples[nremove];
1534 : : else
1535 : 87281 : memmove(state->memtuples, state->memtuples + nremove,
1536 : 87281 : (state->memtupcount - nremove) * sizeof(void *));
1537 : :
5739 1538 : 507537 : state->memtupdeleted = 0;
7038 1539 : 507537 : state->memtupcount -= nremove;
6539 1540 [ + + ]: 2228551 : for (i = 0; i < state->readptrcount; i++)
1541 : : {
1542 [ + + ]: 1721014 : if (!state->readptrs[i].eof_reached)
1543 : 1717958 : state->readptrs[i].current -= nremove;
1544 : : }
1545 : : }
1546 : :
1547 : : /*
1548 : : * tuplestore_updatemax
1549 : : * Update the maximum space used by this tuplestore and the method used
1550 : : * for storage.
1551 : : */
1552 : : static void
783 drowley@postgresql.o 1553 : 589759 : tuplestore_updatemax(Tuplestorestate *state)
1554 : : {
1555 [ + + ]: 589759 : if (state->status == TSS_INMEM)
1556 : 589751 : state->maxSpace = Max(state->maxSpace,
1557 : : state->allowedMem - state->availMem);
1558 : : else
1559 : : {
714 1560 [ + - ]: 8 : state->maxSpace = Max(state->maxSpace,
1561 : : BufFileSize(state->myfile));
1562 : :
1563 : : /*
1564 : : * usedDisk never gets set to false again after spilling to disk, even
1565 : : * if tuplestore_clear() is called and new tuples go to memory again.
1566 : : */
1567 : 8 : state->usedDisk = true;
1568 : : }
783 1569 : 589759 : }
1570 : :
1571 : : /*
1572 : : * tuplestore_get_stats
1573 : : * Obtain statistics about the maximum space used by the tuplestore.
1574 : : * These statistics are the maximums and are not reset by calls to
1575 : : * tuplestore_trim() or tuplestore_clear().
1576 : : */
1577 : : void
714 1578 : 20 : tuplestore_get_stats(Tuplestorestate *state, char **max_storage_type,
1579 : : int64 *max_space)
1580 : : {
783 1581 : 20 : tuplestore_updatemax(state);
1582 : :
714 1583 [ + + ]: 20 : if (state->usedDisk)
1584 : 8 : *max_storage_type = "Disk";
1585 : : else
1586 : 12 : *max_storage_type = "Memory";
1587 : :
1588 : 20 : *max_space = state->maxSpace;
783 1589 : 20 : }
1590 : :
1591 : : /*
1592 : : * tuplestore_in_memory
1593 : : *
1594 : : * Returns true if the tuplestore has not spilled to disk.
1595 : : *
1596 : : * XXX exposing this is a violation of modularity ... should get rid of it.
1597 : : */
1598 : : bool
6451 tgl@sss.pgh.pa.us 1599 : 1154594 : tuplestore_in_memory(Tuplestorestate *state)
1600 : : {
1601 : 1154594 : return (state->status == TSS_INMEM);
1602 : : }
1603 : :
1604 : :
1605 : : /*
1606 : : * Tape interface routines
1607 : : */
1608 : :
1609 : : static unsigned int
9566 1610 : 4440765 : getlen(Tuplestorestate *state, bool eofOK)
1611 : : {
1612 : : unsigned int len;
1613 : : size_t nbytes;
1614 : :
1319 peter@eisentraut.org 1615 : 4440765 : nbytes = BufFileReadMaybeEOF(state->myfile, &len, sizeof(len), eofOK);
1616 [ + + ]: 4440765 : if (nbytes == 0)
1617 : 94 : return 0;
1618 : : else
8572 tgl@sss.pgh.pa.us 1619 : 4440671 : return len;
1620 : : }
1621 : :
1622 : :
1623 : : /*
1624 : : * Routines specialized for HeapTuple case
1625 : : *
1626 : : * The stored form is actually a MinimalTuple, but for largely historical
1627 : : * reasons we allow COPYTUP to work from a HeapTuple.
1628 : : *
1629 : : * Since MinimalTuple already has length in its first word, we don't need
1630 : : * to write that separately.
1631 : : */
1632 : :
1633 : : static void *
9566 1634 : 1147430 : copytup_heap(Tuplestorestate *state, void *tup)
1635 : : {
1636 : : MinimalTuple tuple;
1637 : :
521 jdavis@postgresql.or 1638 : 1147430 : tuple = minimal_tuple_from_heap_tuple((HeapTuple) tup, 0);
8781 tgl@sss.pgh.pa.us 1639 : 1147430 : USEMEM(state, GetMemoryChunkSpace(tuple));
637 peter@eisentraut.org 1640 : 1147430 : return tuple;
1641 : : }
1642 : :
1643 : : static void
9566 tgl@sss.pgh.pa.us 1644 : 4469687 : writetup_heap(Tuplestorestate *state, void *tup)
1645 : : {
7366 1646 : 4469687 : MinimalTuple tuple = (MinimalTuple) tup;
1647 : :
1648 : : /* the part of the MinimalTuple we'll write: */
6512 1649 : 4469687 : char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET;
1650 : 4469687 : unsigned int tupbodylen = tuple->t_len - MINIMAL_TUPLE_DATA_OFFSET;
1651 : :
1652 : : /* total on-disk footprint: */
1653 : 4469687 : unsigned int tuplen = tupbodylen + sizeof(int);
1654 : :
1358 peter@eisentraut.org 1655 : 4469687 : BufFileWrite(state->myfile, &tuplen, sizeof(tuplen));
1656 : 4469687 : BufFileWrite(state->myfile, tupbody, tupbodylen);
6539 tgl@sss.pgh.pa.us 1657 [ - + ]: 4469687 : if (state->backward) /* need trailing length word? */
1358 peter@eisentraut.org 1658 :UBC 0 : BufFileWrite(state->myfile, &tuplen, sizeof(tuplen));
1659 : :
8781 tgl@sss.pgh.pa.us 1660 :CBC 4469687 : FREEMEM(state, GetMemoryChunkSpace(tuple));
7366 1661 : 4469687 : heap_free_minimal_tuple(tuple);
9566 1662 : 4469687 : }
1663 : :
1664 : : static void *
1665 : 4440671 : readtup_heap(Tuplestorestate *state, unsigned int len)
1666 : : {
6512 1667 : 4440671 : unsigned int tupbodylen = len - sizeof(int);
1668 : 4440671 : unsigned int tuplen = tupbodylen + MINIMAL_TUPLE_DATA_OFFSET;
1669 : 4440671 : MinimalTuple tuple = (MinimalTuple) palloc(tuplen);
1670 : 4440671 : char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET;
1671 : :
1672 : : /* read in the tuple proper */
1673 : 4440671 : tuple->t_len = tuplen;
1319 peter@eisentraut.org 1674 : 4440671 : BufFileReadExact(state->myfile, tupbody, tupbodylen);
6539 tgl@sss.pgh.pa.us 1675 [ - + ]: 4440671 : if (state->backward) /* need trailing length word? */
1319 peter@eisentraut.org 1676 :UBC 0 : BufFileReadExact(state->myfile, &tuplen, sizeof(tuplen));
637 peter@eisentraut.org 1677 :CBC 4440671 : return tuple;
1678 : : }
|