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 *
6924 tgl@sss.pgh.pa.us 257 :CBC 148354 : tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
258 : : {
259 : : Tuplestorestate *state;
260 : :
146 michael@paquier.xyz 261 :GNC 148354 : state = palloc0_object(Tuplestorestate);
262 : :
8458 tgl@sss.pgh.pa.us 263 :CBC 148354 : state->status = TSS_INMEM;
6924 264 : 148354 : state->eflags = eflags;
8407 265 : 148354 : state->interXact = interXact;
6338 266 : 148354 : state->truncated = false;
600 drowley@postgresql.o 267 : 148354 : state->usedDisk = false;
268 : 148354 : state->maxSpace = 0;
459 tgl@sss.pgh.pa.us 269 : 148354 : state->allowedMem = maxKBytes * (int64) 1024;
4856 270 : 148354 : state->availMem = state->allowedMem;
9452 271 : 148354 : 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 : : */
669 drowley@postgresql.o 277 : 148354 : state->context = GenerationContextCreate(CurrentMemoryContext,
278 : : "tuplestore tuples",
279 : : ALLOCSET_DEFAULT_SIZES);
5971 heikki.linnakangas@i 280 : 148354 : state->resowner = CurrentResourceOwner;
281 : :
5625 tgl@sss.pgh.pa.us 282 : 148354 : state->memtupdeleted = 0;
9452 283 : 148354 : state->memtupcount = 0;
3322 kgrittn@postgresql.o 284 : 148354 : state->tuples = 0;
285 : :
286 : : /*
287 : : * Initial size of array must be more than ALLOCSET_SEPARATE_THRESHOLD;
288 : : * see comments in grow_memtuples().
289 : : */
3927 tgl@sss.pgh.pa.us 290 : 148354 : state->memtupsize = Max(16384 / sizeof(void *),
291 : : ALLOCSET_SEPARATE_THRESHOLD / sizeof(void *) + 1);
292 : :
4856 293 : 148354 : state->growmemtuples = true;
9452 294 : 148354 : state->memtuples = (void **) palloc(state->memtupsize * sizeof(void *));
295 : :
8667 296 : 148354 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
297 : :
6425 298 : 148354 : state->activeptr = 0;
299 : 148354 : state->readptrcount = 1;
300 : 148354 : state->readptrsize = 8; /* arbitrary */
301 : 148354 : state->readptrs = (TSReadPointer *)
302 : 148354 : palloc(state->readptrsize * sizeof(TSReadPointer));
303 : :
304 : 148354 : state->readptrs[0].eflags = eflags;
305 : 148354 : state->readptrs[0].eof_reached = false;
306 : 148354 : state->readptrs[0].current = 0;
307 : :
9452 308 : 148354 : return state;
309 : : }
310 : :
311 : : /*
312 : : * tuplestore_begin_heap
313 : : *
314 : : * Create a new tuplestore; other types of tuple stores (other than
315 : : * "heap" tuple stores, for heap tuples) are possible, but not presently
316 : : * implemented.
317 : : *
318 : : * randomAccess: if true, both forward and backward accesses to the
319 : : * tuple store are allowed.
320 : : *
321 : : * interXact: if true, the files used for on-disk storage persist beyond the
322 : : * end of the current transaction. NOTE: It's the caller's responsibility to
323 : : * create such a tuplestore in a memory context and resource owner that will
324 : : * also survive transaction boundaries, and to ensure the tuplestore is closed
325 : : * when it's no longer wanted.
326 : : *
327 : : * maxKBytes: how much data to store in memory (any data beyond this
328 : : * amount is paged to disk). When in doubt, use work_mem.
329 : : */
330 : : Tuplestorestate *
8407 331 : 148354 : tuplestore_begin_heap(bool randomAccess, bool interXact, int maxKBytes)
332 : : {
333 : : Tuplestorestate *state;
334 : : int eflags;
335 : :
336 : : /*
337 : : * This interpretation of the meaning of randomAccess is compatible with
338 : : * the pre-8.3 behavior of tuplestores.
339 : : */
6924 340 : 148354 : eflags = randomAccess ?
6425 341 [ + + ]: 148354 : (EXEC_FLAG_BACKWARD | EXEC_FLAG_REWIND) :
342 : : (EXEC_FLAG_REWIND);
343 : :
6924 344 : 148354 : state = tuplestore_begin_common(eflags, interXact, maxKBytes);
345 : :
9452 346 : 148354 : state->copytup = copytup_heap;
347 : 148354 : state->writetup = writetup_heap;
348 : 148354 : state->readtup = readtup_heap;
349 : :
350 : 148354 : return state;
351 : : }
352 : :
353 : : /*
354 : : * tuplestore_set_eflags
355 : : *
356 : : * Set the capability flags for read pointer 0 at a finer grain than is
357 : : * allowed by tuplestore_begin_xxx. This must be called before inserting
358 : : * any data into the tuplestore.
359 : : *
360 : : * eflags is a bitmask following the meanings used for executor node
361 : : * startup flags (see executor.h). tuplestore pays attention to these bits:
362 : : * EXEC_FLAG_REWIND need rewind to start
363 : : * EXEC_FLAG_BACKWARD need backward fetch
364 : : * If tuplestore_set_eflags is not called, REWIND is allowed, and BACKWARD
365 : : * is set per "randomAccess" in the tuplestore_begin_xxx call.
366 : : *
367 : : * NOTE: setting BACKWARD without REWIND means the pointer can read backwards,
368 : : * but not further than the truncation point (the furthest-back read pointer
369 : : * position at the time of the last tuplestore_trim call).
370 : : */
371 : : void
6924 372 : 5084 : tuplestore_set_eflags(Tuplestorestate *state, int eflags)
373 : : {
374 : : int i;
375 : :
6425 376 [ + - - + ]: 5084 : if (state->status != TSS_INMEM || state->memtupcount != 0)
6425 tgl@sss.pgh.pa.us 377 [ # # ]:UBC 0 : elog(ERROR, "too late to call tuplestore_set_eflags");
378 : :
6425 tgl@sss.pgh.pa.us 379 :CBC 5084 : state->readptrs[0].eflags = eflags;
380 [ - + ]: 5084 : for (i = 1; i < state->readptrcount; i++)
6425 tgl@sss.pgh.pa.us 381 :UBC 0 : eflags |= state->readptrs[i].eflags;
6924 tgl@sss.pgh.pa.us 382 :CBC 5084 : state->eflags = eflags;
383 : 5084 : }
384 : :
385 : : /*
386 : : * tuplestore_alloc_read_pointer - allocate another read pointer.
387 : : *
388 : : * Returns the pointer's index.
389 : : *
390 : : * The new pointer initially copies the position of read pointer 0.
391 : : * It can have its own eflags, but if any data has been inserted into
392 : : * the tuplestore, these eflags must not represent an increase in
393 : : * requirements.
394 : : */
395 : : int
6425 396 : 6714 : tuplestore_alloc_read_pointer(Tuplestorestate *state, int eflags)
397 : : {
398 : : /* Check for possible increase of requirements */
399 [ + - + + ]: 6714 : if (state->status != TSS_INMEM || state->memtupcount != 0)
400 : : {
401 [ - + ]: 498 : if ((state->eflags | eflags) != state->eflags)
6425 tgl@sss.pgh.pa.us 402 [ # # ]:UBC 0 : elog(ERROR, "too late to require new tuplestore eflags");
403 : : }
404 : :
405 : : /* Make room for another read pointer if needed */
6425 tgl@sss.pgh.pa.us 406 [ + + ]:CBC 6714 : if (state->readptrcount >= state->readptrsize)
407 : : {
6172 bruce@momjian.us 408 : 20 : int newcnt = state->readptrsize * 2;
409 : :
6425 tgl@sss.pgh.pa.us 410 : 20 : state->readptrs = (TSReadPointer *)
411 : 20 : repalloc(state->readptrs, newcnt * sizeof(TSReadPointer));
412 : 20 : state->readptrsize = newcnt;
413 : : }
414 : :
415 : : /* And set it up */
416 : 6714 : state->readptrs[state->readptrcount] = state->readptrs[0];
417 : 6714 : state->readptrs[state->readptrcount].eflags = eflags;
418 : :
419 : 6714 : state->eflags |= eflags;
420 : :
421 : 6714 : return state->readptrcount++;
422 : : }
423 : :
424 : : /*
425 : : * tuplestore_clear
426 : : *
427 : : * Delete all the contents of a tuplestore, and reset its read pointers
428 : : * to the start.
429 : : */
430 : : void
6422 431 : 6899 : tuplestore_clear(Tuplestorestate *state)
432 : : {
433 : : int i;
434 : : TSReadPointer *readptr;
435 : :
436 : : /* update the maxSpace before doing any USEMEM/FREEMEM adjustments */
669 drowley@postgresql.o 437 : 6899 : tuplestore_updatemax(state);
438 : :
6422 tgl@sss.pgh.pa.us 439 [ + + ]: 6899 : if (state->myfile)
440 : 8 : BufFileClose(state->myfile);
441 : 6899 : state->myfile = NULL;
442 : :
443 : : #ifdef USE_ASSERT_CHECKING
444 : : {
669 drowley@postgresql.o 445 : 6899 : int64 availMem = state->availMem;
446 : :
447 : : /*
448 : : * Below, we reset the memory context for storing tuples. To save
449 : : * from having to always call GetMemoryChunkSpace() on all stored
450 : : * tuples, we adjust the availMem to forget all the tuples and just
451 : : * recall USEMEM for the space used by the memtuples array. Here we
452 : : * just Assert that's correct and the memory tracking hasn't gone
453 : : * wrong anywhere.
454 : : */
5625 tgl@sss.pgh.pa.us 455 [ + + ]: 69331 : for (i = state->memtupdeleted; i < state->memtupcount; i++)
669 drowley@postgresql.o 456 : 62432 : availMem += GetMemoryChunkSpace(state->memtuples[i]);
457 : :
458 : 6899 : availMem += GetMemoryChunkSpace(state->memtuples);
459 : :
460 [ - + ]: 6899 : Assert(availMem == state->allowedMem);
461 : : }
462 : : #endif
463 : :
464 : : /* clear the memory consumed by the memory tuples */
465 : 6899 : MemoryContextReset(state->context);
466 : :
467 : : /*
468 : : * Zero the used memory and re-consume the space for the memtuples array.
469 : : * This saves having to FREEMEM for each stored tuple.
470 : : */
471 : 6899 : state->availMem = state->allowedMem;
472 : 6899 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
473 : :
6422 tgl@sss.pgh.pa.us 474 : 6899 : state->status = TSS_INMEM;
6338 475 : 6899 : state->truncated = false;
5625 476 : 6899 : state->memtupdeleted = 0;
6422 477 : 6899 : state->memtupcount = 0;
3322 kgrittn@postgresql.o 478 : 6899 : state->tuples = 0;
6422 tgl@sss.pgh.pa.us 479 : 6899 : readptr = state->readptrs;
480 [ + + ]: 21539 : for (i = 0; i < state->readptrcount; readptr++, i++)
481 : : {
482 : 14640 : readptr->eof_reached = false;
483 : 14640 : readptr->current = 0;
484 : : }
485 : 6899 : }
486 : :
487 : : /*
488 : : * tuplestore_end
489 : : *
490 : : * Release resources and clean up.
491 : : */
492 : : void
9452 493 : 147727 : tuplestore_end(Tuplestorestate *state)
494 : : {
495 [ + + ]: 147727 : if (state->myfile)
496 : 85 : BufFileClose(state->myfile);
497 : :
669 drowley@postgresql.o 498 : 147727 : MemoryContextDelete(state->context);
499 : 147727 : pfree(state->memtuples);
6425 tgl@sss.pgh.pa.us 500 : 147727 : pfree(state->readptrs);
6851 neilc@samurai.com 501 : 147727 : pfree(state);
9452 tgl@sss.pgh.pa.us 502 : 147727 : }
503 : :
504 : : /*
505 : : * tuplestore_select_read_pointer - make the specified read pointer active
506 : : */
507 : : void
6425 508 : 2914724 : tuplestore_select_read_pointer(Tuplestorestate *state, int ptr)
509 : : {
510 : : TSReadPointer *readptr;
511 : : TSReadPointer *oldptr;
512 : :
513 [ + - - + ]: 2914724 : Assert(ptr >= 0 && ptr < state->readptrcount);
514 : :
515 : : /* No work if already active */
516 [ + + ]: 2914724 : if (ptr == state->activeptr)
517 : 754481 : return;
518 : :
6419 519 : 2160243 : readptr = &state->readptrs[ptr];
520 : 2160243 : oldptr = &state->readptrs[state->activeptr];
521 : :
6425 522 [ + + - ]: 2160243 : switch (state->status)
523 : : {
524 : 2160235 : case TSS_INMEM:
525 : : case TSS_WRITEFILE:
526 : : /* no work */
527 : 2160235 : break;
528 : 8 : case TSS_READFILE:
529 : :
530 : : /*
531 : : * First, save the current read position in the pointer about to
532 : : * become inactive.
533 : : */
6419 534 [ + - ]: 8 : if (!oldptr->eof_reached)
535 : 8 : BufFileTell(state->myfile,
536 : : &oldptr->file,
537 : : &oldptr->offset);
538 : :
539 : : /*
540 : : * We have to make the temp file's seek position equal to the
541 : : * logical position of the new read pointer. In eof_reached
542 : : * state, that's the EOF, which we have available from the saved
543 : : * write position.
544 : : */
6425 545 [ - + ]: 8 : if (readptr->eof_reached)
546 : : {
6425 tgl@sss.pgh.pa.us 547 [ # # ]:UBC 0 : if (BufFileSeek(state->myfile,
548 : : state->writepos_file,
549 : : state->writepos_offset,
550 : : SEEK_SET) != 0)
4345 551 [ # # ]: 0 : ereport(ERROR,
552 : : (errcode_for_file_access(),
553 : : errmsg("could not seek in tuplestore temporary file")));
554 : : }
555 : : else
556 : : {
6425 tgl@sss.pgh.pa.us 557 [ - + ]:CBC 8 : if (BufFileSeek(state->myfile,
558 : : readptr->file,
559 : : readptr->offset,
560 : : SEEK_SET) != 0)
4345 tgl@sss.pgh.pa.us 561 [ # # ]:UBC 0 : ereport(ERROR,
562 : : (errcode_for_file_access(),
563 : : errmsg("could not seek in tuplestore temporary file")));
564 : : }
6425 tgl@sss.pgh.pa.us 565 :CBC 8 : break;
6425 tgl@sss.pgh.pa.us 566 :UBC 0 : default:
567 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
568 : : break;
569 : : }
570 : :
6425 tgl@sss.pgh.pa.us 571 :CBC 2160243 : state->activeptr = ptr;
572 : : }
573 : :
574 : : /*
575 : : * tuplestore_tuple_count
576 : : *
577 : : * Returns the number of tuples added since creation or the last
578 : : * tuplestore_clear().
579 : : */
580 : : int64
3322 kgrittn@postgresql.o 581 : 4406 : tuplestore_tuple_count(Tuplestorestate *state)
582 : : {
583 : 4406 : return state->tuples;
584 : : }
585 : :
586 : : /*
587 : : * tuplestore_ateof
588 : : *
589 : : * Returns the active read pointer's eof_reached state.
590 : : */
591 : : bool
8458 tgl@sss.pgh.pa.us 592 : 3268008 : tuplestore_ateof(Tuplestorestate *state)
593 : : {
6425 594 : 3268008 : return state->readptrs[state->activeptr].eof_reached;
595 : : }
596 : :
597 : : /*
598 : : * Grow the memtuples[] array, if possible within our memory constraint. We
599 : : * must not exceed INT_MAX tuples in memory or the caller-provided memory
600 : : * limit. Return true if we were able to enlarge the array, false if not.
601 : : *
602 : : * Normally, at each increment we double the size of the array. When doing
603 : : * that would exceed a limit, we attempt one last, smaller increase (and then
604 : : * clear the growmemtuples flag so we don't try any more). That allows us to
605 : : * use memory as fully as permitted; sticking to the pure doubling rule could
606 : : * result in almost half going unused. Because availMem moves around with
607 : : * tuple addition/removal, we need some rule to prevent making repeated small
608 : : * increases in memtupsize, which would just be useless thrashing. The
609 : : * growmemtuples flag accomplishes that and also prevents useless
610 : : * recalculations in this function.
611 : : */
612 : : static bool
4856 613 : 1314 : grow_memtuples(Tuplestorestate *state)
614 : : {
615 : : int newmemtupsize;
616 : 1314 : int memtupsize = state->memtupsize;
4688 noah@leadboat.com 617 : 1314 : int64 memNowUsed = state->allowedMem - state->availMem;
618 : :
619 : : /* Forget it if we've already maxed out memtuples, per comment above */
4856 tgl@sss.pgh.pa.us 620 [ + + ]: 1314 : if (!state->growmemtuples)
621 : 26 : return false;
622 : :
623 : : /* Select new value of memtupsize */
624 [ + + ]: 1288 : if (memNowUsed <= state->availMem)
625 : : {
626 : : /*
627 : : * We've used no more than half of allowedMem; double our usage,
628 : : * clamping at INT_MAX tuples.
629 : : */
4695 noah@leadboat.com 630 [ + - ]: 1237 : if (memtupsize < INT_MAX / 2)
631 : 1237 : newmemtupsize = memtupsize * 2;
632 : : else
633 : : {
4695 noah@leadboat.com 634 :UBC 0 : newmemtupsize = INT_MAX;
635 : 0 : state->growmemtuples = false;
636 : : }
637 : : }
638 : : else
639 : : {
640 : : /*
641 : : * This will be the last increment of memtupsize. Abandon doubling
642 : : * strategy and instead increase as much as we safely can.
643 : : *
644 : : * To stay within allowedMem, we can't increase memtupsize by more
645 : : * than availMem / sizeof(void *) elements. In practice, we want to
646 : : * increase it by considerably less, because we need to leave some
647 : : * space for the tuples to which the new array slots will refer. We
648 : : * assume the new tuples will be about the same size as the tuples
649 : : * we've already seen, and thus we can extrapolate from the space
650 : : * consumption so far to estimate an appropriate new size for the
651 : : * memtuples array. The optimal value might be higher or lower than
652 : : * this estimate, but it's hard to know that in advance. We again
653 : : * clamp at INT_MAX tuples.
654 : : *
655 : : * This calculation is safe against enlarging the array so much that
656 : : * LACKMEM becomes true, because the memory currently used includes
657 : : * the present array; thus, there would be enough allowedMem for the
658 : : * new array elements even if no other memory were currently used.
659 : : *
660 : : * We do the arithmetic in float8, because otherwise the product of
661 : : * memtupsize and allowedMem could overflow. Any inaccuracy in the
662 : : * result should be insignificant; but even if we computed a
663 : : * completely insane result, the checks below will prevent anything
664 : : * really bad from happening.
665 : : */
666 : : double grow_ratio;
667 : :
4856 tgl@sss.pgh.pa.us 668 :CBC 51 : grow_ratio = (double) state->allowedMem / (double) memNowUsed;
4695 noah@leadboat.com 669 [ + - ]: 51 : if (memtupsize * grow_ratio < INT_MAX)
670 : 51 : newmemtupsize = (int) (memtupsize * grow_ratio);
671 : : else
4695 noah@leadboat.com 672 :UBC 0 : newmemtupsize = INT_MAX;
673 : :
674 : : /* We won't make any further enlargement attempts */
4856 tgl@sss.pgh.pa.us 675 :CBC 51 : state->growmemtuples = false;
676 : : }
677 : :
678 : : /* Must enlarge array by at least one element, else report failure */
679 [ - + ]: 1288 : if (newmemtupsize <= memtupsize)
4856 tgl@sss.pgh.pa.us 680 :UBC 0 : goto noalloc;
681 : :
682 : : /*
683 : : * On a 32-bit machine, allowedMem could exceed MaxAllocHugeSize. Clamp
684 : : * to ensure our request won't be rejected. Note that we can easily
685 : : * exhaust address space before facing this outcome. (This is presently
686 : : * impossible due to guc.c's MAX_KILOBYTES limitation on work_mem, but
687 : : * don't rely on that at this distance.)
688 : : */
4695 noah@leadboat.com 689 [ - + ]:CBC 1288 : if ((Size) newmemtupsize >= MaxAllocHugeSize / sizeof(void *))
690 : : {
4695 noah@leadboat.com 691 :UBC 0 : newmemtupsize = (int) (MaxAllocHugeSize / sizeof(void *));
4856 tgl@sss.pgh.pa.us 692 : 0 : state->growmemtuples = false; /* can't grow any more */
693 : : }
694 : :
695 : : /*
696 : : * We need to be sure that we do not cause LACKMEM to become true, else
697 : : * the space management algorithm will go nuts. The code above should
698 : : * never generate a dangerous request, but to be safe, check explicitly
699 : : * that the array growth fits within availMem. (We could still cause
700 : : * LACKMEM if the memory chunk overhead associated with the memtuples
701 : : * array were to increase. That shouldn't happen because we chose the
702 : : * initial array size large enough to ensure that palloc will be treating
703 : : * both old and new arrays as separate chunks. But we'll check LACKMEM
704 : : * explicitly below just in case.)
705 : : */
4688 noah@leadboat.com 706 [ - + ]:CBC 1288 : if (state->availMem < (int64) ((newmemtupsize - memtupsize) * sizeof(void *)))
4856 tgl@sss.pgh.pa.us 707 :UBC 0 : goto noalloc;
708 : :
709 : : /* OK, do it */
4856 tgl@sss.pgh.pa.us 710 :CBC 1288 : FREEMEM(state, GetMemoryChunkSpace(state->memtuples));
711 : 1288 : state->memtuples = (void **)
4695 noah@leadboat.com 712 : 1288 : repalloc_huge(state->memtuples,
713 : : newmemtupsize * sizeof(void *));
36 tgl@sss.pgh.pa.us 714 : 1288 : state->memtupsize = newmemtupsize;
4856 715 : 1288 : USEMEM(state, GetMemoryChunkSpace(state->memtuples));
716 [ - + ]: 1288 : if (LACKMEM(state))
3927 tgl@sss.pgh.pa.us 717 [ # # ]:UBC 0 : elog(ERROR, "unexpected out-of-memory situation in tuplestore");
4856 tgl@sss.pgh.pa.us 718 :CBC 1288 : return true;
719 : :
4856 tgl@sss.pgh.pa.us 720 :UBC 0 : noalloc:
721 : : /* If for any reason we didn't realloc, shut off future attempts */
722 : 0 : state->growmemtuples = false;
723 : 0 : return false;
724 : : }
725 : :
726 : : /*
727 : : * Accept one tuple and append it to the tuplestore.
728 : : *
729 : : * Note that the input tuple is always copied; the caller need not save it.
730 : : *
731 : : * If the active read pointer is currently "at EOF", it remains so (the read
732 : : * pointer implicitly advances along with the write pointer); otherwise the
733 : : * read pointer is unchanged. Non-active read pointers do not move, which
734 : : * means they are certain to not be "at EOF" immediately after puttuple.
735 : : * This curious-seeming behavior is for the convenience of nodeMaterial.c and
736 : : * nodeCtescan.c, which would otherwise need to do extra pointer repositioning
737 : : * steps.
738 : : *
739 : : * tuplestore_puttupleslot() is a convenience routine to collect data from
740 : : * a TupleTableSlot without an extra copy operation.
741 : : */
742 : : void
7252 tgl@sss.pgh.pa.us 743 :CBC 1404058 : tuplestore_puttupleslot(Tuplestorestate *state,
744 : : TupleTableSlot *slot)
745 : : {
746 : : MinimalTuple tuple;
5971 heikki.linnakangas@i 747 : 1404058 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
748 : :
749 : : /*
750 : : * Form a MinimalTuple in working memory
751 : : */
7252 tgl@sss.pgh.pa.us 752 : 1404058 : tuple = ExecCopySlotMinimalTuple(slot);
753 : 1404058 : USEMEM(state, GetMemoryChunkSpace(tuple));
754 : :
523 peter@eisentraut.org 755 : 1404058 : tuplestore_puttuple_common(state, tuple);
756 : :
5971 heikki.linnakangas@i 757 : 1404058 : MemoryContextSwitchTo(oldcxt);
7252 tgl@sss.pgh.pa.us 758 : 1404058 : }
759 : :
760 : : /*
761 : : * "Standard" case to copy from a HeapTuple. This is actually now somewhat
762 : : * deprecated, but not worth getting rid of in view of the number of callers.
763 : : */
764 : : void
765 : 1164077 : tuplestore_puttuple(Tuplestorestate *state, HeapTuple tuple)
766 : : {
5971 heikki.linnakangas@i 767 : 1164077 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
768 : :
769 : : /*
770 : : * Copy the tuple. (Must do this even in WRITEFILE case. Note that
771 : : * COPYTUP includes USEMEM, so we needn't do that here.)
772 : : */
9452 tgl@sss.pgh.pa.us 773 : 1164077 : tuple = COPYTUP(state, tuple);
774 : :
523 peter@eisentraut.org 775 : 1164077 : tuplestore_puttuple_common(state, tuple);
776 : :
5971 heikki.linnakangas@i 777 : 1164077 : MemoryContextSwitchTo(oldcxt);
7252 tgl@sss.pgh.pa.us 778 : 1164077 : }
779 : :
780 : : /*
781 : : * Similar to tuplestore_puttuple(), but work from values + nulls arrays.
782 : : * This avoids an extra tuple-construction operation.
783 : : */
784 : : void
6615 neilc@samurai.com 785 : 9861715 : tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc,
786 : : const Datum *values, const bool *isnull)
787 : : {
788 : : MinimalTuple tuple;
5971 heikki.linnakangas@i 789 : 9861715 : MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
790 : :
407 jdavis@postgresql.or 791 : 9861715 : tuple = heap_form_minimal_tuple(tdesc, values, isnull, 0);
5438 tgl@sss.pgh.pa.us 792 : 9861715 : USEMEM(state, GetMemoryChunkSpace(tuple));
793 : :
523 peter@eisentraut.org 794 : 9861715 : tuplestore_puttuple_common(state, tuple);
795 : :
5912 bruce@momjian.us 796 : 9861715 : MemoryContextSwitchTo(oldcxt);
6615 neilc@samurai.com 797 : 9861715 : }
798 : :
799 : : static void
7252 tgl@sss.pgh.pa.us 800 : 12429850 : tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
801 : : {
802 : : TSReadPointer *readptr;
803 : : int i;
804 : : ResourceOwner oldowner;
805 : : MemoryContext oldcxt;
806 : :
3322 kgrittn@postgresql.o 807 : 12429850 : state->tuples++;
808 : :
9452 tgl@sss.pgh.pa.us 809 [ + + + - ]: 12429850 : switch (state->status)
810 : : {
8458 811 : 10233704 : case TSS_INMEM:
812 : :
813 : : /*
814 : : * Update read pointers as needed; see API spec above.
815 : : */
6422 816 : 10233704 : readptr = state->readptrs;
817 [ + + ]: 22135626 : for (i = 0; i < state->readptrcount; readptr++, i++)
818 : : {
819 [ + + + + ]: 11901922 : if (readptr->eof_reached && i != state->activeptr)
820 : : {
821 : 314 : readptr->eof_reached = false;
822 : 314 : readptr->current = state->memtupcount;
823 : : }
824 : : }
825 : :
826 : : /*
827 : : * Grow the array as needed. Note that we try to grow the array
828 : : * when there is still one free slot remaining --- if we fail,
829 : : * there'll still be room to store the incoming tuple, and then
830 : : * we'll switch to tape-based operation.
831 : : */
7367 832 [ + + ]: 10233704 : if (state->memtupcount >= state->memtupsize - 1)
833 : : {
4856 834 : 1314 : (void) grow_memtuples(state);
835 [ - + ]: 1314 : Assert(state->memtupcount < state->memtupsize);
836 : : }
837 : :
838 : : /* Stash the tuple in the in-memory array */
9452 839 : 10233704 : state->memtuples[state->memtupcount++] = tuple;
840 : :
841 : : /*
842 : : * Done if we still fit in available memory and have array slots.
843 : : */
7367 844 [ + + + + ]: 10233704 : if (state->memtupcount < state->memtupsize && !LACKMEM(state))
9452 845 : 10233610 : return;
846 : :
847 : : /*
848 : : * Nope; time to switch to tape-based operation. Make sure that
849 : : * the temp file(s) are created in suitable temp tablespaces.
850 : : */
6907 851 : 94 : PrepareTempTablespaces();
852 : :
853 : : /* associate the file with the store's resource owner */
5971 heikki.linnakangas@i 854 : 94 : oldowner = CurrentResourceOwner;
855 : 94 : CurrentResourceOwner = state->resowner;
856 : :
857 : : /*
858 : : * We switch out of the state->context as this is a generation
859 : : * context, which isn't ideal for allocations relating to the
860 : : * BufFile.
861 : : */
668 drowley@postgresql.o 862 : 94 : oldcxt = MemoryContextSwitchTo(state->context->parent);
863 : :
6907 tgl@sss.pgh.pa.us 864 : 94 : state->myfile = BufFileCreateTemp(state->interXact);
865 : :
668 drowley@postgresql.o 866 : 94 : MemoryContextSwitchTo(oldcxt);
867 : :
5971 heikki.linnakangas@i 868 : 94 : CurrentResourceOwner = oldowner;
869 : :
870 : : /*
871 : : * Freeze the decision about whether trailing length words will be
872 : : * used. We can't change this choice once data is on tape, even
873 : : * though callers might drop the requirement.
874 : : */
6425 tgl@sss.pgh.pa.us 875 : 94 : state->backward = (state->eflags & EXEC_FLAG_BACKWARD) != 0;
876 : :
877 : : /*
878 : : * Update the maximum space used before dumping the tuples. It's
879 : : * possible that more space will be used by the tuples in memory
880 : : * than the space that will be used on disk.
881 : : */
600 drowley@postgresql.o 882 : 94 : tuplestore_updatemax(state);
883 : :
9452 tgl@sss.pgh.pa.us 884 : 94 : state->status = TSS_WRITEFILE;
885 : 94 : dumptuples(state);
886 : 94 : break;
887 : 2196138 : case TSS_WRITEFILE:
888 : :
889 : : /*
890 : : * Update read pointers as needed; see API spec above. Note:
891 : : * BufFileTell is quite cheap, so not worth trying to avoid
892 : : * multiple calls.
893 : : */
6422 894 : 2196138 : readptr = state->readptrs;
895 [ + + ]: 4403248 : for (i = 0; i < state->readptrcount; readptr++, i++)
896 : : {
897 [ - + - - ]: 2207110 : if (readptr->eof_reached && i != state->activeptr)
898 : : {
6422 tgl@sss.pgh.pa.us 899 :UBC 0 : readptr->eof_reached = false;
900 : 0 : BufFileTell(state->myfile,
901 : : &readptr->file,
902 : : &readptr->offset);
903 : : }
904 : : }
905 : :
9452 tgl@sss.pgh.pa.us 906 :CBC 2196138 : WRITETUP(state, tuple);
907 : 2196138 : break;
8458 908 : 8 : case TSS_READFILE:
909 : :
910 : : /*
911 : : * Switch from reading to writing.
912 : : */
6425 913 [ + - ]: 8 : if (!state->readptrs[state->activeptr].eof_reached)
8458 914 : 8 : BufFileTell(state->myfile,
6425 915 : 8 : &state->readptrs[state->activeptr].file,
916 : 8 : &state->readptrs[state->activeptr].offset);
8458 917 [ - + ]: 8 : if (BufFileSeek(state->myfile,
918 : : state->writepos_file, state->writepos_offset,
919 : : SEEK_SET) != 0)
4345 tgl@sss.pgh.pa.us 920 [ # # ]:UBC 0 : ereport(ERROR,
921 : : (errcode_for_file_access(),
922 : : errmsg("could not seek in tuplestore temporary file")));
8458 tgl@sss.pgh.pa.us 923 :CBC 8 : state->status = TSS_WRITEFILE;
924 : :
925 : : /*
926 : : * Update read pointers as needed; see API spec above.
927 : : */
6422 928 : 8 : readptr = state->readptrs;
929 [ + + ]: 24 : for (i = 0; i < state->readptrcount; readptr++, i++)
930 : : {
931 [ - + - - ]: 16 : if (readptr->eof_reached && i != state->activeptr)
932 : : {
6422 tgl@sss.pgh.pa.us 933 :UBC 0 : readptr->eof_reached = false;
934 : 0 : readptr->file = state->writepos_file;
935 : 0 : readptr->offset = state->writepos_offset;
936 : : }
937 : : }
938 : :
8458 tgl@sss.pgh.pa.us 939 :CBC 8 : WRITETUP(state, tuple);
9452 940 : 8 : break;
9452 tgl@sss.pgh.pa.us 941 :UBC 0 : default:
8320 942 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
943 : : break;
944 : : }
945 : : }
946 : :
947 : : /*
948 : : * Fetch the next tuple in either forward or back direction.
949 : : * Returns NULL if no more tuples. If should_free is set, the
950 : : * caller must pfree the returned tuple when done with it.
951 : : *
952 : : * Backward scan is only allowed if randomAccess was set true or
953 : : * EXEC_FLAG_BACKWARD was specified to tuplestore_set_eflags().
954 : : */
955 : : static void *
9452 tgl@sss.pgh.pa.us 956 :CBC 16106350 : tuplestore_gettuple(Tuplestorestate *state, bool forward,
957 : : bool *should_free)
958 : : {
6425 959 : 16106350 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
960 : : unsigned int tuplen;
961 : : void *tup;
962 : :
963 [ + + - + ]: 16106350 : Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD));
964 : :
9452 965 [ + + + - ]: 16106350 : switch (state->status)
966 : : {
8458 967 : 11566040 : case TSS_INMEM:
9452 968 : 11566040 : *should_free = false;
969 [ + + ]: 11566040 : if (forward)
970 : : {
6425 971 [ + + ]: 11448388 : if (readptr->eof_reached)
972 : 129 : return NULL;
973 [ + + ]: 11448259 : if (readptr->current < state->memtupcount)
974 : : {
975 : : /* We have another tuple, so return it */
976 : 11204656 : return state->memtuples[readptr->current++];
977 : : }
978 : 243603 : readptr->eof_reached = true;
9452 979 : 243603 : return NULL;
980 : : }
981 : : else
982 : : {
983 : : /*
984 : : * if all tuples are fetched already then we return last
985 : : * tuple, else tuple before last returned.
986 : : */
6425 987 [ + + ]: 117652 : if (readptr->eof_reached)
988 : : {
989 : 1946 : readptr->current = state->memtupcount;
990 : 1946 : readptr->eof_reached = false;
991 : : }
992 : : else
993 : : {
5625 994 [ - + ]: 115706 : if (readptr->current <= state->memtupdeleted)
995 : : {
6338 tgl@sss.pgh.pa.us 996 [ # # ]:UBC 0 : Assert(!state->truncated);
9452 997 : 0 : return NULL;
998 : : }
6172 bruce@momjian.us 999 :CBC 115706 : readptr->current--; /* last returned tuple */
1000 : : }
5625 tgl@sss.pgh.pa.us 1001 [ + + ]: 117652 : if (readptr->current <= state->memtupdeleted)
1002 : : {
6338 1003 [ - + ]: 21 : Assert(!state->truncated);
6425 1004 : 21 : return NULL;
1005 : : }
1006 : 117631 : return state->memtuples[readptr->current - 1];
1007 : : }
1008 : : break;
1009 : :
8458 1010 : 102 : case TSS_WRITEFILE:
1011 : : /* Skip state change if we'll just return NULL */
6425 1012 [ - + - - ]: 102 : if (readptr->eof_reached && forward)
8458 tgl@sss.pgh.pa.us 1013 :UBC 0 : return NULL;
1014 : :
1015 : : /*
1016 : : * Switch from writing to reading.
1017 : : */
8458 tgl@sss.pgh.pa.us 1018 :CBC 102 : BufFileTell(state->myfile,
1019 : : &state->writepos_file, &state->writepos_offset);
6425 1020 [ + - ]: 102 : if (!readptr->eof_reached)
8458 1021 [ - + ]: 102 : if (BufFileSeek(state->myfile,
1022 : : readptr->file, readptr->offset,
1023 : : SEEK_SET) != 0)
4345 tgl@sss.pgh.pa.us 1024 [ # # ]:UBC 0 : ereport(ERROR,
1025 : : (errcode_for_file_access(),
1026 : : errmsg("could not seek in tuplestore temporary file")));
8458 tgl@sss.pgh.pa.us 1027 :CBC 102 : state->status = TSS_READFILE;
1028 : : pg_fallthrough;
1029 : :
9452 1030 : 4540310 : case TSS_READFILE:
1031 : 4540310 : *should_free = true;
1032 [ + - ]: 4540310 : if (forward)
1033 : : {
1034 [ + + ]: 4540310 : if ((tuplen = getlen(state, true)) != 0)
1035 : : {
1036 : 4540214 : tup = READTUP(state, tuplen);
1037 : 4540214 : return tup;
1038 : : }
1039 : : else
1040 : : {
6425 1041 : 96 : readptr->eof_reached = true;
9452 1042 : 96 : return NULL;
1043 : : }
1044 : : }
1045 : :
1046 : : /*
1047 : : * Backward.
1048 : : *
1049 : : * if all tuples are fetched already then we return last tuple,
1050 : : * else tuple before last returned.
1051 : : *
1052 : : * Back up to fetch previously-returned tuple's ending length
1053 : : * word. If seek fails, assume we are at start of file.
1054 : : */
133 michael@paquier.xyz 1055 [ # # ]:UNC 0 : if (BufFileSeek(state->myfile, 0, -(pgoff_t) sizeof(unsigned int),
1056 : : SEEK_CUR) != 0)
1057 : : {
1058 : : /* even a failed backwards fetch gets you out of eof state */
6425 tgl@sss.pgh.pa.us 1059 :UBC 0 : readptr->eof_reached = false;
6338 1060 [ # # ]: 0 : Assert(!state->truncated);
8458 1061 : 0 : return NULL;
1062 : : }
1063 : 0 : tuplen = getlen(state, false);
1064 : :
6425 1065 [ # # ]: 0 : if (readptr->eof_reached)
1066 : : {
1067 : 0 : readptr->eof_reached = false;
1068 : : /* We will return the tuple returned before returning NULL */
1069 : : }
1070 : : else
1071 : : {
1072 : : /*
1073 : : * Back up to get ending length word of tuple before it.
1074 : : */
9452 1075 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0,
133 michael@paquier.xyz 1076 :UNC 0 : -(pgoff_t) (tuplen + 2 * sizeof(unsigned int)),
1077 : : SEEK_CUR) != 0)
1078 : : {
1079 : : /*
1080 : : * If that fails, presumably the prev tuple is the first
1081 : : * in the file. Back up so that it becomes next to read
1082 : : * in forward direction (not obviously right, but that is
1083 : : * what in-memory case does).
1084 : : */
9452 tgl@sss.pgh.pa.us 1085 [ # # ]:UBC 0 : if (BufFileSeek(state->myfile, 0,
133 michael@paquier.xyz 1086 :UNC 0 : -(pgoff_t) (tuplen + sizeof(unsigned int)),
1087 : : SEEK_CUR) != 0)
4345 tgl@sss.pgh.pa.us 1088 [ # # ]:UBC 0 : ereport(ERROR,
1089 : : (errcode_for_file_access(),
1090 : : errmsg("could not seek in tuplestore temporary file")));
6338 1091 [ # # ]: 0 : Assert(!state->truncated);
9452 1092 : 0 : return NULL;
1093 : : }
8458 1094 : 0 : tuplen = getlen(state, false);
1095 : : }
1096 : :
1097 : : /*
1098 : : * Now we have the length of the prior tuple, back up and read it.
1099 : : * Note: READTUP expects we are positioned after the initial
1100 : : * length word of the tuple, so back up to that point.
1101 : : */
9452 1102 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0,
133 michael@paquier.xyz 1103 :UNC 0 : -(pgoff_t) tuplen,
1104 : : SEEK_CUR) != 0)
4345 tgl@sss.pgh.pa.us 1105 [ # # ]:UBC 0 : ereport(ERROR,
1106 : : (errcode_for_file_access(),
1107 : : errmsg("could not seek in tuplestore temporary file")));
9452 1108 : 0 : tup = READTUP(state, tuplen);
1109 : 0 : return tup;
1110 : :
1111 : 0 : default:
8320 1112 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1113 : : return NULL; /* keep compiler quiet */
1114 : : }
1115 : : }
1116 : :
1117 : : /*
1118 : : * tuplestore_gettupleslot - exported function to fetch a MinimalTuple
1119 : : *
1120 : : * If successful, put tuple in slot and return true; else, clear the slot
1121 : : * and return false.
1122 : : *
1123 : : * If copy is true, the slot receives a copied tuple (allocated in current
1124 : : * memory context) that will stay valid regardless of future manipulations of
1125 : : * the tuplestore's state. If copy is false, the slot may just receive a
1126 : : * pointer to a tuple held within the tuplestore. The latter is more
1127 : : * efficient but the slot contents may be corrupted if additional writes to
1128 : : * the tuplestore occur. (If using tuplestore_trim, see comments therein.)
1129 : : */
1130 : : bool
7252 tgl@sss.pgh.pa.us 1131 :CBC 15990666 : tuplestore_gettupleslot(Tuplestorestate *state, bool forward,
1132 : : bool copy, TupleTableSlot *slot)
1133 : : {
1134 : : MinimalTuple tuple;
1135 : : bool should_free;
1136 : :
1137 : 15990666 : tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free);
1138 : :
1139 [ + + ]: 15990666 : if (tuple)
1140 : : {
6248 1141 [ + + + + ]: 15748809 : if (copy && !should_free)
1142 : : {
407 jdavis@postgresql.or 1143 : 1322929 : tuple = heap_copy_minimal_tuple(tuple, 0);
6248 tgl@sss.pgh.pa.us 1144 : 1322929 : should_free = true;
1145 : : }
7252 1146 : 15748809 : ExecStoreMinimalTuple(tuple, slot, should_free);
1147 : 15748809 : return true;
1148 : : }
1149 : : else
1150 : : {
1151 : 241857 : ExecClearTuple(slot);
1152 : 241857 : return false;
1153 : : }
1154 : : }
1155 : :
1156 : : /*
1157 : : * tuplestore_gettupleslot_force - exported function to fetch a tuple
1158 : : *
1159 : : * This is identical to tuplestore_gettupleslot except the given slot can be
1160 : : * any kind of slot; it need not be one that will accept a MinimalTuple.
1161 : : */
1162 : : bool
47 tgl@sss.pgh.pa.us 1163 :GNC 219 : tuplestore_gettupleslot_force(Tuplestorestate *state, bool forward,
1164 : : bool copy, TupleTableSlot *slot)
1165 : : {
1166 : : MinimalTuple tuple;
1167 : : bool should_free;
1168 : :
1169 : 219 : tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free);
1170 : :
1171 [ + + ]: 219 : if (tuple)
1172 : : {
1173 [ - + - - ]: 133 : if (copy && !should_free)
1174 : : {
47 tgl@sss.pgh.pa.us 1175 :UNC 0 : tuple = heap_copy_minimal_tuple(tuple, 0);
1176 : 0 : should_free = true;
1177 : : }
47 tgl@sss.pgh.pa.us 1178 :GNC 133 : ExecForceStoreMinimalTuple(tuple, slot, should_free);
1179 : 133 : return true;
1180 : : }
1181 : : else
1182 : : {
1183 : 86 : ExecClearTuple(slot);
1184 : 86 : return false;
1185 : : }
1186 : : }
1187 : :
1188 : : /*
1189 : : * tuplestore_advance - exported function to adjust position without fetching
1190 : : *
1191 : : * We could optimize this case to avoid palloc/pfree overhead, but for the
1192 : : * moment it doesn't seem worthwhile.
1193 : : */
1194 : : bool
7252 tgl@sss.pgh.pa.us 1195 :CBC 115465 : tuplestore_advance(Tuplestorestate *state, bool forward)
1196 : : {
1197 : : void *tuple;
1198 : : bool should_free;
1199 : :
1200 : 115465 : tuple = tuplestore_gettuple(state, forward, &should_free);
1201 : :
1202 [ + + ]: 115465 : if (tuple)
1203 : : {
1204 [ - + ]: 113559 : if (should_free)
7252 tgl@sss.pgh.pa.us 1205 :UBC 0 : pfree(tuple);
7252 tgl@sss.pgh.pa.us 1206 :CBC 113559 : return true;
1207 : : }
1208 : : else
1209 : : {
1210 : 1906 : return false;
1211 : : }
1212 : : }
1213 : :
1214 : : /*
1215 : : * Advance over N tuples in either forward or back direction,
1216 : : * without returning any data. N<=0 is a no-op.
1217 : : * Returns true if successful, false if ran out of tuples.
1218 : : */
1219 : : bool
4405 1220 : 891270 : tuplestore_skiptuples(Tuplestorestate *state, int64 ntuples, bool forward)
1221 : : {
1222 : 891270 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
1223 : :
1224 [ + + - + ]: 891270 : Assert(forward || (readptr->eflags & EXEC_FLAG_BACKWARD));
1225 : :
1226 [ + + ]: 891270 : if (ntuples <= 0)
1227 : 12 : return true;
1228 : :
1229 [ + - ]: 891258 : switch (state->status)
1230 : : {
1231 : 891258 : case TSS_INMEM:
1232 [ + + ]: 891258 : if (forward)
1233 : : {
1234 [ - + ]: 889392 : if (readptr->eof_reached)
4405 tgl@sss.pgh.pa.us 1235 :UBC 0 : return false;
4405 tgl@sss.pgh.pa.us 1236 [ + + ]:CBC 889392 : if (state->memtupcount - readptr->current >= ntuples)
1237 : : {
1238 : 889315 : readptr->current += ntuples;
1239 : 889315 : return true;
1240 : : }
1241 : 77 : readptr->current = state->memtupcount;
1242 : 77 : readptr->eof_reached = true;
1243 : 77 : return false;
1244 : : }
1245 : : else
1246 : : {
1247 [ - + ]: 1866 : if (readptr->eof_reached)
1248 : : {
4405 tgl@sss.pgh.pa.us 1249 :UBC 0 : readptr->current = state->memtupcount;
1250 : 0 : readptr->eof_reached = false;
1251 : 0 : ntuples--;
1252 : : }
4405 tgl@sss.pgh.pa.us 1253 [ + - ]:CBC 1866 : if (readptr->current - state->memtupdeleted > ntuples)
1254 : : {
1255 : 1866 : readptr->current -= ntuples;
1256 : 1866 : return true;
1257 : : }
4405 tgl@sss.pgh.pa.us 1258 [ # # ]:UBC 0 : Assert(!state->truncated);
1259 : 0 : readptr->current = state->memtupdeleted;
1260 : 0 : return false;
1261 : : }
1262 : : break;
1263 : :
1264 : 0 : default:
1265 : : /* We don't currently try hard to optimize other cases */
1266 [ # # ]: 0 : while (ntuples-- > 0)
1267 : : {
1268 : : void *tuple;
1269 : : bool should_free;
1270 : :
1271 : 0 : tuple = tuplestore_gettuple(state, forward, &should_free);
1272 : :
1273 [ # # ]: 0 : if (tuple == NULL)
1274 : 0 : return false;
1275 [ # # ]: 0 : if (should_free)
1276 : 0 : pfree(tuple);
1277 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1278 : : }
1279 : 0 : return true;
1280 : : }
1281 : : }
1282 : :
1283 : : /*
1284 : : * dumptuples - remove tuples from memory and write to tape
1285 : : *
1286 : : * As a side effect, we must convert each read pointer's position from
1287 : : * "current" to file/offset format. But eof_reached pointers don't
1288 : : * need to change state.
1289 : : */
1290 : : static void
9452 tgl@sss.pgh.pa.us 1291 :CBC 94 : dumptuples(Tuplestorestate *state)
1292 : : {
1293 : : int i;
1294 : :
5625 1295 : 94 : for (i = state->memtupdeleted;; i++)
8458 1296 : 2373084 : {
6425 1297 : 2373178 : TSReadPointer *readptr = state->readptrs;
1298 : : int j;
1299 : :
1300 [ + + ]: 4755376 : for (j = 0; j < state->readptrcount; readptr++, j++)
1301 : : {
1302 [ + + + - ]: 2382198 : if (i == readptr->current && !readptr->eof_reached)
1303 : 102 : BufFileTell(state->myfile,
1304 : : &readptr->file, &readptr->offset);
1305 : : }
8458 1306 [ + + ]: 2373178 : if (i >= state->memtupcount)
1307 : 94 : break;
9452 1308 : 2373084 : WRITETUP(state, state->memtuples[i]);
1309 : :
1310 : : /*
1311 : : * Increase memtupdeleted to track the fact that we just deleted that
1312 : : * tuple. Think not to remove this on the grounds that we'll reset
1313 : : * memtupdeleted to zero below. We might not reach that if some later
1314 : : * WRITETUP fails (e.g. due to overrunning temp_file_limit). If so,
1315 : : * we'd error out leaving an effectively-corrupt tuplestore, which
1316 : : * would be quite bad if it's a persistent data structure such as a
1317 : : * Portal's holdStore.
1318 : : */
36 1319 : 2373084 : state->memtupdeleted++;
1320 : : }
1321 : : /* Now we can reset memtupdeleted along with memtupcount */
5625 1322 : 94 : state->memtupdeleted = 0;
9452 1323 : 94 : state->memtupcount = 0;
1324 : 94 : }
1325 : :
1326 : : /*
1327 : : * tuplestore_rescan - rewind the active read pointer to start
1328 : : */
1329 : : void
1330 : 200465 : tuplestore_rescan(Tuplestorestate *state)
1331 : : {
6425 1332 : 200465 : TSReadPointer *readptr = &state->readptrs[state->activeptr];
1333 : :
1334 [ - + ]: 200465 : Assert(readptr->eflags & EXEC_FLAG_REWIND);
6338 1335 [ - + ]: 200465 : Assert(!state->truncated);
1336 : :
9452 1337 [ + + - - ]: 200465 : switch (state->status)
1338 : : {
8458 1339 : 200387 : case TSS_INMEM:
6425 1340 : 200387 : readptr->eof_reached = false;
1341 : 200387 : readptr->current = 0;
8458 1342 : 200387 : break;
1343 : 78 : case TSS_WRITEFILE:
6425 1344 : 78 : readptr->eof_reached = false;
1345 : 78 : readptr->file = 0;
1133 peter@eisentraut.org 1346 : 78 : readptr->offset = 0;
9452 tgl@sss.pgh.pa.us 1347 : 78 : break;
9452 tgl@sss.pgh.pa.us 1348 :UBC 0 : case TSS_READFILE:
6425 1349 : 0 : readptr->eof_reached = false;
1133 peter@eisentraut.org 1350 [ # # ]: 0 : if (BufFileSeek(state->myfile, 0, 0, SEEK_SET) != 0)
4345 tgl@sss.pgh.pa.us 1351 [ # # ]: 0 : ereport(ERROR,
1352 : : (errcode_for_file_access(),
1353 : : errmsg("could not seek in tuplestore temporary file")));
9452 1354 : 0 : break;
1355 : 0 : default:
8320 1356 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1357 : : break;
1358 : : }
9452 tgl@sss.pgh.pa.us 1359 :CBC 200465 : }
1360 : :
1361 : : /*
1362 : : * tuplestore_copy_read_pointer - copy a read pointer's state to another
1363 : : */
1364 : : void
6425 1365 : 40344 : tuplestore_copy_read_pointer(Tuplestorestate *state,
1366 : : int srcptr, int destptr)
1367 : : {
1368 : 40344 : TSReadPointer *sptr = &state->readptrs[srcptr];
1369 : 40344 : TSReadPointer *dptr = &state->readptrs[destptr];
1370 : :
1371 [ + - - + ]: 40344 : Assert(srcptr >= 0 && srcptr < state->readptrcount);
1372 [ + - - + ]: 40344 : Assert(destptr >= 0 && destptr < state->readptrcount);
1373 : :
1374 : : /* Assigning to self is a no-op */
1375 [ - + ]: 40344 : if (srcptr == destptr)
6425 tgl@sss.pgh.pa.us 1376 :UBC 0 : return;
1377 : :
6425 tgl@sss.pgh.pa.us 1378 [ - + ]:CBC 40344 : if (dptr->eflags != sptr->eflags)
1379 : : {
1380 : : /* Possible change of overall eflags, so copy and then recompute */
1381 : : int eflags;
1382 : : int i;
1383 : :
6425 tgl@sss.pgh.pa.us 1384 :UBC 0 : *dptr = *sptr;
1385 : 0 : eflags = state->readptrs[0].eflags;
1386 [ # # ]: 0 : for (i = 1; i < state->readptrcount; i++)
1387 : 0 : eflags |= state->readptrs[i].eflags;
1388 : 0 : state->eflags = eflags;
1389 : : }
1390 : : else
6425 tgl@sss.pgh.pa.us 1391 :CBC 40344 : *dptr = *sptr;
1392 : :
9452 1393 [ + - - ]: 40344 : switch (state->status)
1394 : : {
8458 1395 : 40344 : case TSS_INMEM:
1396 : : case TSS_WRITEFILE:
1397 : : /* no work */
9452 1398 : 40344 : break;
9452 tgl@sss.pgh.pa.us 1399 :UBC 0 : case TSS_READFILE:
1400 : :
1401 : : /*
1402 : : * This case is a bit tricky since the active read pointer's
1403 : : * position corresponds to the seek point, not what is in its
1404 : : * variables. Assigning to the active requires a seek, and
1405 : : * assigning from the active requires a tell, except when
1406 : : * eof_reached.
1407 : : */
6425 1408 [ # # ]: 0 : if (destptr == state->activeptr)
1409 : : {
1410 [ # # ]: 0 : if (dptr->eof_reached)
1411 : : {
1412 [ # # ]: 0 : if (BufFileSeek(state->myfile,
1413 : : state->writepos_file,
1414 : : state->writepos_offset,
1415 : : SEEK_SET) != 0)
4345 1416 [ # # ]: 0 : ereport(ERROR,
1417 : : (errcode_for_file_access(),
1418 : : errmsg("could not seek in tuplestore temporary file")));
1419 : : }
1420 : : else
1421 : : {
6425 1422 [ # # ]: 0 : if (BufFileSeek(state->myfile,
1423 : : dptr->file, dptr->offset,
1424 : : SEEK_SET) != 0)
4345 1425 [ # # ]: 0 : ereport(ERROR,
1426 : : (errcode_for_file_access(),
1427 : : errmsg("could not seek in tuplestore temporary file")));
1428 : : }
1429 : : }
6425 1430 [ # # ]: 0 : else if (srcptr == state->activeptr)
1431 : : {
1432 [ # # ]: 0 : if (!dptr->eof_reached)
1433 : 0 : BufFileTell(state->myfile,
1434 : : &dptr->file,
1435 : : &dptr->offset);
1436 : : }
9452 1437 : 0 : break;
1438 : 0 : default:
8320 1439 [ # # ]: 0 : elog(ERROR, "invalid tuplestore state");
1440 : : break;
1441 : : }
1442 : : }
1443 : :
1444 : : /*
1445 : : * tuplestore_trim - remove all no-longer-needed tuples
1446 : : *
1447 : : * Calling this function authorizes the tuplestore to delete all tuples
1448 : : * before the oldest read pointer, if no read pointer is marked as requiring
1449 : : * REWIND capability.
1450 : : *
1451 : : * Note: this is obviously safe if no pointer has BACKWARD capability either.
1452 : : * If a pointer is marked as BACKWARD but not REWIND capable, it means that
1453 : : * the pointer can be moved backward but not before the oldest other read
1454 : : * pointer.
1455 : : */
1456 : : void
6425 tgl@sss.pgh.pa.us 1457 :CBC 607896 : tuplestore_trim(Tuplestorestate *state)
1458 : : {
1459 : : int oldest;
1460 : : int nremove;
1461 : : int i;
1462 : :
1463 : : /*
1464 : : * Truncation is disallowed if any read pointer requires rewind
1465 : : * capability.
1466 : : */
6338 1467 [ - + ]: 607896 : if (state->eflags & EXEC_FLAG_REWIND)
6425 tgl@sss.pgh.pa.us 1468 :UBC 0 : return;
1469 : :
1470 : : /*
1471 : : * We don't bother trimming temp files since it usually would mean more
1472 : : * work than just letting them sit in kernel buffers until they age out.
1473 : : */
6924 tgl@sss.pgh.pa.us 1474 [ + + ]:CBC 607896 : if (state->status != TSS_INMEM)
1475 : 19992 : return;
1476 : :
1477 : : /* Find the oldest read pointer */
6425 1478 : 587904 : oldest = state->memtupcount;
1479 [ + + ]: 2560749 : for (i = 0; i < state->readptrcount; i++)
1480 : : {
1481 [ + + ]: 1972845 : if (!state->readptrs[i].eof_reached)
1482 : 1953095 : oldest = Min(oldest, state->readptrs[i].current);
1483 : : }
1484 : :
1485 : : /*
1486 : : * Note: you might think we could remove all the tuples before the oldest
1487 : : * "current", since that one is the next to be returned. However, since
1488 : : * tuplestore_gettuple returns a direct pointer to our internal copy of
1489 : : * the tuple, it's likely that the caller has still got the tuple just
1490 : : * before "current" referenced in a slot. So we keep one extra tuple
1491 : : * before the oldest "current". (Strictly speaking, we could require such
1492 : : * callers to use the "copy" flag to tuplestore_gettupleslot, but for
1493 : : * efficiency we allow this one case to not use "copy".)
1494 : : */
1495 : 587904 : nremove = oldest - 1;
6924 1496 [ + + ]: 587904 : if (nremove <= 0)
1497 : 5286 : return; /* nothing to do */
1498 : :
5625 1499 [ - + ]: 582618 : Assert(nremove >= state->memtupdeleted);
6924 1500 [ - + ]: 582618 : Assert(nremove <= state->memtupcount);
1501 : :
1502 : : /* before freeing any memory, update the statistics */
669 drowley@postgresql.o 1503 : 582618 : tuplestore_updatemax(state);
1504 : :
1505 : : /* Release no-longer-needed tuples */
5625 tgl@sss.pgh.pa.us 1506 [ + + ]: 1165932 : for (i = state->memtupdeleted; i < nremove; i++)
1507 : : {
6924 1508 : 583314 : FREEMEM(state, GetMemoryChunkSpace(state->memtuples[i]));
1509 : 583314 : pfree(state->memtuples[i]);
5625 1510 : 583314 : state->memtuples[i] = NULL;
1511 : : /* As in dumptuples(), increment memtupdeleted synchronously */
36 1512 : 583314 : state->memtupdeleted++;
1513 : : }
1514 [ - + ]: 582618 : Assert(state->memtupdeleted == nremove);
1515 : :
1516 : : /* mark tuplestore as truncated (used for Assert crosschecks only) */
5625 1517 : 582618 : state->truncated = true;
1518 : :
1519 : : /*
1520 : : * If nremove is less than 1/8th memtupcount, just stop here, leaving the
1521 : : * "deleted" slots as NULL. This prevents us from expending O(N^2) time
1522 : : * repeatedly memmove-ing a large pointer array. The worst case space
1523 : : * wastage is pretty small, since it's just pointers and not whole tuples.
1524 : : */
1525 [ + + ]: 582618 : if (nremove < state->memtupcount / 8)
1526 : 75120 : return;
1527 : :
1528 : : /*
1529 : : * Slide the array down and readjust pointers.
1530 : : *
1531 : : * In mergejoin's current usage, it's demonstrable that there will always
1532 : : * be exactly one non-removed tuple; so optimize that case.
1533 : : */
6924 1534 [ + + ]: 507498 : if (nremove + 1 == state->memtupcount)
1535 : 420230 : state->memtuples[0] = state->memtuples[nremove];
1536 : : else
1537 : 87268 : memmove(state->memtuples, state->memtuples + nremove,
1538 : 87268 : (state->memtupcount - nremove) * sizeof(void *));
1539 : :
5625 1540 : 507498 : state->memtupdeleted = 0;
6924 1541 : 507498 : state->memtupcount -= nremove;
6425 1542 [ + + ]: 2228401 : for (i = 0; i < state->readptrcount; i++)
1543 : : {
1544 [ + + ]: 1720903 : if (!state->readptrs[i].eof_reached)
1545 : 1717851 : state->readptrs[i].current -= nremove;
1546 : : }
1547 : : }
1548 : :
1549 : : /*
1550 : : * tuplestore_updatemax
1551 : : * Update the maximum space used by this tuplestore and the method used
1552 : : * for storage.
1553 : : */
1554 : : static void
669 drowley@postgresql.o 1555 : 589631 : tuplestore_updatemax(Tuplestorestate *state)
1556 : : {
1557 [ + + ]: 589631 : if (state->status == TSS_INMEM)
1558 : 589623 : state->maxSpace = Max(state->maxSpace,
1559 : : state->allowedMem - state->availMem);
1560 : : else
1561 : : {
600 1562 [ + - ]: 8 : state->maxSpace = Max(state->maxSpace,
1563 : : BufFileSize(state->myfile));
1564 : :
1565 : : /*
1566 : : * usedDisk never gets set to false again after spilling to disk, even
1567 : : * if tuplestore_clear() is called and new tuples go to memory again.
1568 : : */
1569 : 8 : state->usedDisk = true;
1570 : : }
669 1571 : 589631 : }
1572 : :
1573 : : /*
1574 : : * tuplestore_get_stats
1575 : : * Obtain statistics about the maximum space used by the tuplestore.
1576 : : * These statistics are the maximums and are not reset by calls to
1577 : : * tuplestore_trim() or tuplestore_clear().
1578 : : */
1579 : : void
600 1580 : 20 : tuplestore_get_stats(Tuplestorestate *state, char **max_storage_type,
1581 : : int64 *max_space)
1582 : : {
669 1583 : 20 : tuplestore_updatemax(state);
1584 : :
600 1585 [ + + ]: 20 : if (state->usedDisk)
1586 : 8 : *max_storage_type = "Disk";
1587 : : else
1588 : 12 : *max_storage_type = "Memory";
1589 : :
1590 : 20 : *max_space = state->maxSpace;
669 1591 : 20 : }
1592 : :
1593 : : /*
1594 : : * tuplestore_in_memory
1595 : : *
1596 : : * Returns true if the tuplestore has not spilled to disk.
1597 : : *
1598 : : * XXX exposing this is a violation of modularity ... should get rid of it.
1599 : : */
1600 : : bool
6337 tgl@sss.pgh.pa.us 1601 : 1154470 : tuplestore_in_memory(Tuplestorestate *state)
1602 : : {
1603 : 1154470 : return (state->status == TSS_INMEM);
1604 : : }
1605 : :
1606 : :
1607 : : /*
1608 : : * Tape interface routines
1609 : : */
1610 : :
1611 : : static unsigned int
9452 1612 : 4540310 : getlen(Tuplestorestate *state, bool eofOK)
1613 : : {
1614 : : unsigned int len;
1615 : : size_t nbytes;
1616 : :
1205 peter@eisentraut.org 1617 : 4540310 : nbytes = BufFileReadMaybeEOF(state->myfile, &len, sizeof(len), eofOK);
1618 [ + + ]: 4540310 : if (nbytes == 0)
1619 : 96 : return 0;
1620 : : else
8458 tgl@sss.pgh.pa.us 1621 : 4540214 : return len;
1622 : : }
1623 : :
1624 : :
1625 : : /*
1626 : : * Routines specialized for HeapTuple case
1627 : : *
1628 : : * The stored form is actually a MinimalTuple, but for largely historical
1629 : : * reasons we allow COPYTUP to work from a HeapTuple.
1630 : : *
1631 : : * Since MinimalTuple already has length in its first word, we don't need
1632 : : * to write that separately.
1633 : : */
1634 : :
1635 : : static void *
9452 1636 : 1164077 : copytup_heap(Tuplestorestate *state, void *tup)
1637 : : {
1638 : : MinimalTuple tuple;
1639 : :
407 jdavis@postgresql.or 1640 : 1164077 : tuple = minimal_tuple_from_heap_tuple((HeapTuple) tup, 0);
8667 tgl@sss.pgh.pa.us 1641 : 1164077 : USEMEM(state, GetMemoryChunkSpace(tuple));
523 peter@eisentraut.org 1642 : 1164077 : return tuple;
1643 : : }
1644 : :
1645 : : static void
9452 tgl@sss.pgh.pa.us 1646 : 4569230 : writetup_heap(Tuplestorestate *state, void *tup)
1647 : : {
7252 1648 : 4569230 : MinimalTuple tuple = (MinimalTuple) tup;
1649 : :
1650 : : /* the part of the MinimalTuple we'll write: */
6398 1651 : 4569230 : char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET;
1652 : 4569230 : unsigned int tupbodylen = tuple->t_len - MINIMAL_TUPLE_DATA_OFFSET;
1653 : :
1654 : : /* total on-disk footprint: */
1655 : 4569230 : unsigned int tuplen = tupbodylen + sizeof(int);
1656 : :
1244 peter@eisentraut.org 1657 : 4569230 : BufFileWrite(state->myfile, &tuplen, sizeof(tuplen));
1658 : 4569230 : BufFileWrite(state->myfile, tupbody, tupbodylen);
6425 tgl@sss.pgh.pa.us 1659 [ - + ]: 4569230 : if (state->backward) /* need trailing length word? */
1244 peter@eisentraut.org 1660 :UBC 0 : BufFileWrite(state->myfile, &tuplen, sizeof(tuplen));
1661 : :
8667 tgl@sss.pgh.pa.us 1662 :CBC 4569230 : FREEMEM(state, GetMemoryChunkSpace(tuple));
7252 1663 : 4569230 : heap_free_minimal_tuple(tuple);
9452 1664 : 4569230 : }
1665 : :
1666 : : static void *
1667 : 4540214 : readtup_heap(Tuplestorestate *state, unsigned int len)
1668 : : {
6398 1669 : 4540214 : unsigned int tupbodylen = len - sizeof(int);
1670 : 4540214 : unsigned int tuplen = tupbodylen + MINIMAL_TUPLE_DATA_OFFSET;
1671 : 4540214 : MinimalTuple tuple = (MinimalTuple) palloc(tuplen);
1672 : 4540214 : char *tupbody = (char *) tuple + MINIMAL_TUPLE_DATA_OFFSET;
1673 : :
1674 : : /* read in the tuple proper */
1675 : 4540214 : tuple->t_len = tuplen;
1205 peter@eisentraut.org 1676 : 4540214 : BufFileReadExact(state->myfile, tupbody, tupbodylen);
6425 tgl@sss.pgh.pa.us 1677 [ - + ]: 4540214 : if (state->backward) /* need trailing length word? */
1205 peter@eisentraut.org 1678 :UBC 0 : BufFileReadExact(state->myfile, &tuplen, sizeof(tuplen));
523 peter@eisentraut.org 1679 :CBC 4540214 : return tuple;
1680 : : }
|