Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * contrib/pageinspect/btreefuncs.c
3 : : *
4 : : *
5 : : * btreefuncs.c
6 : : *
7 : : * Copyright (c) 2006 Satoshi Nagayasu <nagayasus@nttdata.co.jp>
8 : : *
9 : : * Permission to use, copy, modify, and distribute this software and
10 : : * its documentation for any purpose, without fee, and without a
11 : : * written agreement is hereby granted, provided that the above
12 : : * copyright notice and this paragraph and the following two
13 : : * paragraphs appear in all copies.
14 : : *
15 : : * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
16 : : * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
17 : : * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 : : * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
19 : : * OF THE POSSIBILITY OF SUCH DAMAGE.
20 : : *
21 : : * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
22 : : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 : : * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
24 : : * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
25 : : * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
26 : : */
27 : :
28 : : #include "postgres.h"
29 : :
30 : : #include "access/nbtree.h"
31 : : #include "access/relation.h"
32 : : #include "catalog/namespace.h"
33 : : #include "catalog/pg_am.h"
34 : : #include "catalog/pg_type.h"
35 : : #include "funcapi.h"
36 : : #include "miscadmin.h"
37 : : #include "pageinspect.h"
38 : : #include "utils/array.h"
39 : : #include "utils/builtins.h"
40 : : #include "utils/rel.h"
41 : : #include "utils/varlena.h"
42 : :
6076 tgl@sss.pgh.pa.us 43 :CBC 25 : PG_FUNCTION_INFO_V1(bt_metap);
1181 peter@eisentraut.org 44 : 7 : PG_FUNCTION_INFO_V1(bt_page_items_1_9);
6076 tgl@sss.pgh.pa.us 45 : 13 : PG_FUNCTION_INFO_V1(bt_page_items);
2567 peter_e@gmx.net 46 : 13 : PG_FUNCTION_INFO_V1(bt_page_items_bytea);
1181 peter@eisentraut.org 47 : 7 : PG_FUNCTION_INFO_V1(bt_page_stats_1_9);
6076 tgl@sss.pgh.pa.us 48 : 7 : PG_FUNCTION_INFO_V1(bt_page_stats);
468 49 : 6 : PG_FUNCTION_INFO_V1(bt_multi_page_stats);
50 : :
51 : : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
52 : : #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
53 : :
54 : : /* ------------------------------------------------
55 : : * structure for single btree page statistics
56 : : * ------------------------------------------------
57 : : */
58 : : typedef struct BTPageStat
59 : : {
60 : : uint32 blkno;
61 : : uint32 live_items;
62 : : uint32 dead_items;
63 : : uint32 page_size;
64 : : uint32 max_avail;
65 : : uint32 free_size;
66 : : uint32 avg_item_size;
67 : : char type;
68 : :
69 : : /* opaque data */
70 : : BlockNumber btpo_prev;
71 : : BlockNumber btpo_next;
72 : : uint32 btpo_level;
73 : : uint16 btpo_flags;
74 : : BTCycleId btpo_cycleid;
75 : : } BTPageStat;
76 : :
77 : : /*
78 : : * cross-call data structure for SRF for page stats
79 : : */
80 : : typedef struct ua_page_stats
81 : : {
82 : : Oid relid;
83 : : int64 blkno;
84 : : int64 blk_count;
85 : : bool allpages;
86 : : } ua_page_stats;
87 : :
88 : : /*
89 : : * cross-call data structure for SRF for page items
90 : : */
91 : : typedef struct ua_page_items
92 : : {
93 : : Page page;
94 : : OffsetNumber offset;
95 : : bool leafpage;
96 : : bool rightmost;
97 : : TupleDesc tupd;
98 : : } ua_page_items;
99 : :
100 : :
101 : : /* -------------------------------------------------
102 : : * GetBTPageStatistics()
103 : : *
104 : : * Collect statistics of single b-tree page
105 : : * -------------------------------------------------
106 : : */
107 : : static void
5421 bruce@momjian.us 108 : 10 : GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat)
109 : : {
2916 kgrittn@postgresql.o 110 : 10 : Page page = BufferGetPage(buffer);
6177 bruce@momjian.us 111 : 10 : PageHeader phdr = (PageHeader) page;
112 : 10 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
744 michael@paquier.xyz 113 : 10 : BTPageOpaque opaque = BTPageGetOpaque(page);
6177 bruce@momjian.us 114 : 10 : int item_size = 0;
115 : : int off;
116 : :
117 : 10 : stat->blkno = blkno;
118 : :
119 : 10 : stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData);
120 : :
121 : 10 : stat->dead_items = stat->live_items = 0;
122 : :
123 : 10 : stat->page_size = PageGetPageSize(page);
124 : :
125 : : /* page type (flags) */
126 [ - + ]: 10 : if (P_ISDELETED(opaque))
127 : : {
128 : : /* We divide deleted pages into leaf ('d') or internal ('D') */
1145 pg@bowt.ie 129 [ # # # # ]:UBC 0 : if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque))
130 : 0 : stat->type = 'd';
131 : : else
132 : 0 : stat->type = 'D';
133 : :
134 : : /*
135 : : * Report safexid in a deleted page.
136 : : *
137 : : * Handle pg_upgrade'd deleted pages that used the previous safexid
138 : : * representation in btpo_level field (this used to be a union type
139 : : * called "bpto").
140 : : */
141 [ # # ]: 0 : if (P_HAS_FULLXID(opaque))
142 : : {
143 : 0 : FullTransactionId safexid = BTPageGetDeleteXid(page);
144 : :
940 145 [ # # ]: 0 : elog(DEBUG2, "deleted page from block %u has safexid %u:%u",
146 : : blkno, EpochFromFullTransactionId(safexid),
147 : : XidFromFullTransactionId(safexid));
148 : : }
149 : : else
150 [ # # ]: 0 : elog(DEBUG2, "deleted page from block %u has safexid %u",
151 : : blkno, opaque->btpo_level);
152 : :
153 : : /* Don't interpret BTDeletedPageData as index tuples */
1145 154 : 0 : maxoff = InvalidOffsetNumber;
155 : : }
6177 bruce@momjian.us 156 [ - + ]:CBC 10 : else if (P_IGNORE(opaque))
6177 bruce@momjian.us 157 :UBC 0 : stat->type = 'e';
6177 bruce@momjian.us 158 [ + + ]:CBC 10 : else if (P_ISLEAF(opaque))
159 : 8 : stat->type = 'l';
160 [ + - ]: 2 : else if (P_ISROOT(opaque))
161 : 2 : stat->type = 'r';
162 : : else
6177 bruce@momjian.us 163 :UBC 0 : stat->type = 'i';
164 : :
165 : : /* btpage opaque data */
6177 bruce@momjian.us 166 :CBC 10 : stat->btpo_prev = opaque->btpo_prev;
167 : 10 : stat->btpo_next = opaque->btpo_next;
1145 pg@bowt.ie 168 : 10 : stat->btpo_level = opaque->btpo_level;
6177 bruce@momjian.us 169 : 10 : stat->btpo_flags = opaque->btpo_flags;
170 : 10 : stat->btpo_cycleid = opaque->btpo_cycleid;
171 : :
172 : : /* count live and dead tuples, and free space */
173 [ + + ]: 2022 : for (off = FirstOffsetNumber; off <= maxoff; off++)
174 : : {
175 : : IndexTuple itup;
176 : :
177 : 2012 : ItemId id = PageGetItemId(page, off);
178 : :
179 : 2012 : itup = (IndexTuple) PageGetItem(page, id);
180 : :
181 : 2012 : item_size += IndexTupleSize(itup);
182 : :
6059 tgl@sss.pgh.pa.us 183 [ + - ]: 2012 : if (!ItemIdIsDead(id))
6177 bruce@momjian.us 184 : 2012 : stat->live_items++;
185 : : else
6177 bruce@momjian.us 186 :UBC 0 : stat->dead_items++;
187 : : }
6177 bruce@momjian.us 188 :CBC 10 : stat->free_size = PageGetFreeSpace(page);
189 : :
190 [ + - ]: 10 : if ((stat->live_items + stat->dead_items) > 0)
191 : 10 : stat->avg_item_size = item_size / (stat->live_items + stat->dead_items);
192 : : else
6177 bruce@momjian.us 193 :UBC 0 : stat->avg_item_size = 0;
6177 bruce@momjian.us 194 :CBC 10 : }
195 : :
196 : : /* -----------------------------------------------
197 : : * check_relation_block_range()
198 : : *
199 : : * Verify that a block number (given as int64) is valid for the relation.
200 : : * -----------------------------------------------
201 : : */
202 : : static void
468 tgl@sss.pgh.pa.us 203 : 15 : check_relation_block_range(Relation rel, int64 blkno)
204 : : {
205 : : /* Ensure we can cast to BlockNumber */
206 [ + + - + ]: 15 : if (blkno < 0 || blkno > MaxBlockNumber)
6076 207 [ + - ]: 2 : ereport(ERROR,
208 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
209 : : errmsg("invalid block number %lld",
210 : : (long long) blkno)));
211 : :
468 212 [ + + ]: 13 : if ((BlockNumber) (blkno) >= RelationGetNumberOfBlocks(rel))
213 [ + - ]: 3 : ereport(ERROR,
214 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
215 : : errmsg("block number %lld is out of range",
216 : : (long long) blkno)));
217 : 10 : }
218 : :
219 : : /* -----------------------------------------------
220 : : * bt_index_block_validate()
221 : : *
222 : : * Validate index type is btree and block number
223 : : * is valid (and not the metapage).
224 : : * -----------------------------------------------
225 : : */
226 : : static void
227 : 18 : bt_index_block_validate(Relation rel, int64 blkno)
228 : : {
6177 bruce@momjian.us 229 [ + - + + ]: 18 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
760 michael@paquier.xyz 230 [ + - ]: 2 : ereport(ERROR,
231 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
232 : : errmsg("\"%s\" is not a %s index",
233 : : RelationGetRelationName(rel), "btree")));
234 : :
235 : : /*
236 : : * Reject attempts to read non-local temporary relations; we would be
237 : : * likely to get wrong data since we have no visibility into the owning
238 : : * session's local buffers.
239 : : */
5493 tgl@sss.pgh.pa.us 240 [ - + - - ]: 16 : if (RELATION_IS_OTHER_TEMP(rel))
5493 tgl@sss.pgh.pa.us 241 [ # # ]:UBC 0 : ereport(ERROR,
242 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
243 : : errmsg("cannot access temporary tables of other sessions")));
244 : :
6177 bruce@momjian.us 245 [ + + ]:CBC 16 : if (blkno == 0)
1181 peter@eisentraut.org 246 [ + - ]: 3 : ereport(ERROR,
247 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
248 : : errmsg("block 0 is a meta page")));
249 : :
468 tgl@sss.pgh.pa.us 250 : 13 : check_relation_block_range(rel, blkno);
251 : 8 : }
252 : :
253 : : /* -----------------------------------------------
254 : : * bt_page_stats()
255 : : *
256 : : * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1);
257 : : * Arguments are index relation name and block number
258 : : * -----------------------------------------------
259 : : */
260 : : static Datum
261 : 6 : bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
262 : : {
263 : 6 : text *relname = PG_GETARG_TEXT_PP(0);
264 [ + + ]: 6 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
265 : : Buffer buffer;
266 : : Relation rel;
267 : : RangeVar *relrv;
268 : : Datum result;
269 : : HeapTuple tuple;
270 : : TupleDesc tupleDesc;
271 : : int j;
272 : : char *values[11];
273 : : BTPageStat stat;
274 : :
275 [ - + ]: 6 : if (!superuser())
468 tgl@sss.pgh.pa.us 276 [ # # ]:UBC 0 : ereport(ERROR,
277 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
278 : : errmsg("must be superuser to use pageinspect functions")));
279 : :
468 tgl@sss.pgh.pa.us 280 :CBC 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
281 : 6 : rel = relation_openrv(relrv, AccessShareLock);
282 : :
283 : 6 : bt_index_block_validate(rel, blkno);
284 : :
6076 285 : 2 : buffer = ReadBuffer(rel, blkno);
4153 286 : 2 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
287 : :
288 : : /* keep compiler quiet */
6076 289 : 2 : stat.btpo_prev = stat.btpo_next = InvalidBlockNumber;
290 : 2 : stat.btpo_flags = stat.free_size = stat.avg_item_size = 0;
291 : :
292 : 2 : GetBTPageStatistics(blkno, buffer, &stat);
293 : :
4153 294 : 2 : UnlockReleaseBuffer(buffer);
295 : 2 : relation_close(rel, AccessShareLock);
296 : :
297 : : /* Build a tuple descriptor for our result type */
6076 298 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
6076 tgl@sss.pgh.pa.us 299 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
300 : :
6076 tgl@sss.pgh.pa.us 301 :CBC 2 : j = 0;
1181 peter@eisentraut.org 302 : 2 : values[j++] = psprintf("%u", stat.blkno);
3751 peter_e@gmx.net 303 : 2 : values[j++] = psprintf("%c", stat.type);
1181 peter@eisentraut.org 304 : 2 : values[j++] = psprintf("%u", stat.live_items);
305 : 2 : values[j++] = psprintf("%u", stat.dead_items);
306 : 2 : values[j++] = psprintf("%u", stat.avg_item_size);
307 : 2 : values[j++] = psprintf("%u", stat.page_size);
308 : 2 : values[j++] = psprintf("%u", stat.free_size);
309 : 2 : values[j++] = psprintf("%u", stat.btpo_prev);
310 : 2 : values[j++] = psprintf("%u", stat.btpo_next);
1145 pg@bowt.ie 311 : 2 : values[j++] = psprintf("%u", stat.btpo_level);
3751 peter_e@gmx.net 312 : 2 : values[j++] = psprintf("%d", stat.btpo_flags);
313 : :
6076 tgl@sss.pgh.pa.us 314 : 2 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
315 : : values);
316 : :
317 : 2 : result = HeapTupleGetDatum(tuple);
318 : :
6177 bruce@momjian.us 319 : 2 : PG_RETURN_DATUM(result);
320 : : }
321 : :
322 : : Datum
1181 peter@eisentraut.org 323 : 5 : bt_page_stats_1_9(PG_FUNCTION_ARGS)
324 : : {
325 : 5 : return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_9);
326 : : }
327 : :
328 : : /* entry point for old extension version */
329 : : Datum
330 : 1 : bt_page_stats(PG_FUNCTION_ARGS)
331 : : {
332 : 1 : return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_8);
333 : : }
334 : :
335 : :
336 : : /* -----------------------------------------------
337 : : * bt_multi_page_stats()
338 : : *
339 : : * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1, 2);
340 : : * Arguments are index relation name, first block number, number of blocks
341 : : * (but number of blocks can be negative to mean "read all the rest")
342 : : * -----------------------------------------------
343 : : */
344 : : Datum
468 tgl@sss.pgh.pa.us 345 : 14 : bt_multi_page_stats(PG_FUNCTION_ARGS)
346 : : {
347 : : Relation rel;
348 : : ua_page_stats *uargs;
349 : : FuncCallContext *fctx;
350 : : MemoryContext mctx;
351 : :
352 [ - + ]: 14 : if (!superuser())
468 tgl@sss.pgh.pa.us 353 [ # # ]:UBC 0 : ereport(ERROR,
354 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
355 : : errmsg("must be superuser to use pageinspect functions")));
356 : :
468 tgl@sss.pgh.pa.us 357 [ + + ]:CBC 14 : if (SRF_IS_FIRSTCALL())
358 : : {
359 : 6 : text *relname = PG_GETARG_TEXT_PP(0);
360 : 6 : int64 blkno = PG_GETARG_INT64(1);
361 : 6 : int64 blk_count = PG_GETARG_INT64(2);
362 : : RangeVar *relrv;
363 : :
364 : 6 : fctx = SRF_FIRSTCALL_INIT();
365 : :
366 : 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
367 : 6 : rel = relation_openrv(relrv, AccessShareLock);
368 : :
369 : : /* Check that rel is a valid btree index and 1st block number is OK */
370 : 6 : bt_index_block_validate(rel, blkno);
371 : :
372 : : /*
373 : : * Check if upper bound of the specified range is valid. If only one
374 : : * page is requested, skip as we've already validated the page. (Also,
375 : : * it's important to skip this if blk_count is negative.)
376 : : */
377 [ + + ]: 4 : if (blk_count > 1)
378 : 2 : check_relation_block_range(rel, blkno + blk_count - 1);
379 : :
380 : : /* Save arguments for reuse */
381 : 4 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
382 : :
383 : 4 : uargs = palloc(sizeof(ua_page_stats));
384 : :
385 : 4 : uargs->relid = RelationGetRelid(rel);
386 : 4 : uargs->blkno = blkno;
387 : 4 : uargs->blk_count = blk_count;
388 : 4 : uargs->allpages = (blk_count < 0);
389 : :
390 : 4 : fctx->user_fctx = uargs;
391 : :
392 : 4 : MemoryContextSwitchTo(mctx);
393 : :
394 : : /*
395 : : * To avoid possibly leaking a relcache reference if the SRF isn't run
396 : : * to completion, we close and re-open the index rel each time
397 : : * through, using the index's OID for re-opens to ensure we get the
398 : : * same rel. Keep the AccessShareLock though, to ensure it doesn't go
399 : : * away underneath us.
400 : : */
401 : 4 : relation_close(rel, NoLock);
402 : : }
403 : :
404 : 12 : fctx = SRF_PERCALL_SETUP();
405 : 12 : uargs = fctx->user_fctx;
406 : :
407 : : /* We should have lock already */
408 : 12 : rel = relation_open(uargs->relid, NoLock);
409 : :
410 : : /* In all-pages mode, recheck the index length each time */
411 [ + + ]: 12 : if (uargs->allpages)
412 : 5 : uargs->blk_count = RelationGetNumberOfBlocks(rel) - uargs->blkno;
413 : :
414 [ + + ]: 12 : if (uargs->blk_count > 0)
415 : : {
416 : : /* We need to fetch next block statistics */
417 : : Buffer buffer;
418 : : Datum result;
419 : : HeapTuple tuple;
420 : : int j;
421 : : char *values[11];
422 : : BTPageStat stat;
423 : : TupleDesc tupleDesc;
424 : :
425 : 8 : buffer = ReadBuffer(rel, uargs->blkno);
426 : 8 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
427 : :
428 : : /* keep compiler quiet */
429 : 8 : stat.btpo_prev = stat.btpo_next = InvalidBlockNumber;
430 : 8 : stat.btpo_flags = stat.free_size = stat.avg_item_size = 0;
431 : :
432 : 8 : GetBTPageStatistics(uargs->blkno, buffer, &stat);
433 : :
434 : 8 : UnlockReleaseBuffer(buffer);
435 : 8 : relation_close(rel, NoLock);
436 : :
437 : : /* Build a tuple descriptor for our result type */
438 [ - + ]: 8 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
468 tgl@sss.pgh.pa.us 439 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
440 : :
468 tgl@sss.pgh.pa.us 441 :CBC 8 : j = 0;
442 : 8 : values[j++] = psprintf("%u", stat.blkno);
443 : 8 : values[j++] = psprintf("%c", stat.type);
444 : 8 : values[j++] = psprintf("%u", stat.live_items);
445 : 8 : values[j++] = psprintf("%u", stat.dead_items);
446 : 8 : values[j++] = psprintf("%u", stat.avg_item_size);
447 : 8 : values[j++] = psprintf("%u", stat.page_size);
448 : 8 : values[j++] = psprintf("%u", stat.free_size);
449 : 8 : values[j++] = psprintf("%u", stat.btpo_prev);
450 : 8 : values[j++] = psprintf("%u", stat.btpo_next);
451 : 8 : values[j++] = psprintf("%u", stat.btpo_level);
452 : 8 : values[j++] = psprintf("%d", stat.btpo_flags);
453 : :
454 : : /* Construct tuple to be returned */
455 : 8 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
456 : : values);
457 : :
458 : 8 : result = HeapTupleGetDatum(tuple);
459 : :
460 : : /*
461 : : * Move to the next block number and decrement the number of blocks
462 : : * still to be fetched
463 : : */
464 : 8 : uargs->blkno++;
465 : 8 : uargs->blk_count--;
466 : :
467 : 8 : SRF_RETURN_NEXT(fctx, result);
468 : : }
469 : :
470 : : /* Done, so finally we can release the index lock */
471 : 4 : relation_close(rel, AccessShareLock);
472 : 4 : SRF_RETURN_DONE(fctx);
473 : : }
474 : :
475 : : /*-------------------------------------------------------
476 : : * bt_page_print_tuples()
477 : : *
478 : : * Form a tuple describing index tuple at a given offset
479 : : * ------------------------------------------------------
480 : : */
481 : : static Datum
482 : 3 : bt_page_print_tuples(ua_page_items *uargs)
483 : : {
1506 pg@bowt.ie 484 : 3 : Page page = uargs->page;
485 : 3 : OffsetNumber offset = uargs->offset;
486 : 3 : bool leafpage = uargs->leafpage;
487 : 3 : bool rightmost = uargs->rightmost;
488 : : bool ispivottuple;
489 : : Datum values[9];
490 : : bool nulls[9];
491 : : HeapTuple tuple;
492 : : ItemId id;
493 : : IndexTuple itup;
494 : : int j;
495 : : int off;
496 : : int dlen;
497 : : char *dump,
498 : : *datacstring;
499 : : char *ptr;
500 : : ItemPointer htid;
501 : :
2567 peter_e@gmx.net 502 : 3 : id = PageGetItemId(page, offset);
503 : :
504 [ - + ]: 3 : if (!ItemIdIsValid(id))
2567 peter_e@gmx.net 505 [ # # ]:UBC 0 : elog(ERROR, "invalid ItemId");
506 : :
2567 peter_e@gmx.net 507 :CBC 3 : itup = (IndexTuple) PageGetItem(page, id);
508 : :
509 : 3 : j = 0;
1506 pg@bowt.ie 510 : 3 : memset(nulls, 0, sizeof(nulls));
511 : 3 : values[j++] = DatumGetInt16(offset);
512 : 3 : values[j++] = ItemPointerGetDatum(&itup->t_tid);
513 : 3 : values[j++] = Int32GetDatum((int) IndexTupleSize(itup));
514 : 3 : values[j++] = BoolGetDatum(IndexTupleHasNulls(itup));
515 : 3 : values[j++] = BoolGetDatum(IndexTupleHasVarwidths(itup));
516 : :
2567 peter_e@gmx.net 517 : 3 : ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
518 : 3 : dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info);
519 : :
520 : : /*
521 : : * Make sure that "data" column does not include posting list or pivot
522 : : * tuple representation of heap TID(s).
523 : : *
524 : : * Note: BTreeTupleIsPivot() won't work reliably on !heapkeyspace indexes
525 : : * (those built before BTREE_VERSION 4), but we have no way of determining
526 : : * if this page came from a !heapkeyspace index. We may only have a bytea
527 : : * nbtree page image to go on, so in general there is no metapage that we
528 : : * can check.
529 : : *
530 : : * That's okay here because BTreeTupleIsPivot() can only return false for
531 : : * a !heapkeyspace pivot, never true for a !heapkeyspace non-pivot. Since
532 : : * heap TID isn't part of the keyspace in a !heapkeyspace index anyway,
533 : : * there cannot possibly be a pivot tuple heap TID representation that we
534 : : * fail to make an adjustment for. A !heapkeyspace index can have
535 : : * BTreeTupleIsPivot() return true (due to things like suffix truncation
536 : : * for INCLUDE indexes in Postgres v11), but when that happens
537 : : * BTreeTupleGetHeapTID() can be trusted to work reliably (i.e. return
538 : : * NULL).
539 : : *
540 : : * Note: BTreeTupleIsPosting() always works reliably, even with
541 : : * !heapkeyspace indexes.
542 : : */
1506 pg@bowt.ie 543 [ - + ]: 3 : if (BTreeTupleIsPosting(itup))
1506 pg@bowt.ie 544 :UBC 0 : dlen -= IndexTupleSize(itup) - BTreeTupleGetPostingOffset(itup);
1506 pg@bowt.ie 545 [ - + - - ]:CBC 3 : else if (BTreeTupleIsPivot(itup) && BTreeTupleGetHeapTID(itup) != NULL)
1506 pg@bowt.ie 546 :UBC 0 : dlen -= MAXALIGN(sizeof(ItemPointerData));
547 : :
1506 pg@bowt.ie 548 [ + - - + ]:CBC 3 : if (dlen < 0 || dlen > INDEX_SIZE_MASK)
1506 pg@bowt.ie 549 [ # # ]:UBC 0 : elog(ERROR, "invalid tuple length %d for tuple at offset number %u",
550 : : dlen, offset);
2567 peter_e@gmx.net 551 :CBC 3 : dump = palloc0(dlen * 3 + 1);
1506 pg@bowt.ie 552 : 3 : datacstring = dump;
2567 peter_e@gmx.net 553 [ + + ]: 27 : for (off = 0; off < dlen; off++)
554 : : {
555 [ + + ]: 24 : if (off > 0)
556 : 21 : *dump++ = ' ';
557 : 24 : sprintf(dump, "%02x", *(ptr + off) & 0xff);
558 : 24 : dump += 2;
559 : : }
1506 pg@bowt.ie 560 : 3 : values[j++] = CStringGetTextDatum(datacstring);
561 : 3 : pfree(datacstring);
562 : :
563 : : /*
564 : : * We need to work around the BTreeTupleIsPivot() !heapkeyspace limitation
565 : : * again. Deduce whether or not tuple must be a pivot tuple based on
566 : : * whether or not the page is a leaf page, as well as the page offset
567 : : * number of the tuple.
568 : : */
569 [ + - - + : 3 : ispivottuple = (!leafpage || (!rightmost && offset == P_HIKEY));
- - ]
570 : :
571 : : /* LP_DEAD bit can never be set for pivot tuples, so show a NULL there */
572 [ + - ]: 3 : if (!ispivottuple)
573 : 3 : values[j++] = BoolGetDatum(ItemIdIsDead(id));
574 : : else
575 : : {
1506 pg@bowt.ie 576 [ # # ]:UBC 0 : Assert(!ItemIdIsDead(id));
577 : 0 : nulls[j++] = true;
578 : : }
579 : :
1506 pg@bowt.ie 580 :CBC 3 : htid = BTreeTupleGetHeapTID(itup);
581 [ - + - - ]: 3 : if (ispivottuple && !BTreeTupleIsPivot(itup))
582 : : {
583 : : /* Don't show bogus heap TID in !heapkeyspace pivot tuple */
1506 pg@bowt.ie 584 :UBC 0 : htid = NULL;
585 : : }
586 : :
1506 pg@bowt.ie 587 [ + - ]:CBC 3 : if (htid)
588 : 3 : values[j++] = ItemPointerGetDatum(htid);
589 : : else
1506 pg@bowt.ie 590 :UBC 0 : nulls[j++] = true;
591 : :
1506 pg@bowt.ie 592 [ - + ]:CBC 3 : if (BTreeTupleIsPosting(itup))
593 : : {
594 : : /* Build an array of item pointers */
595 : : ItemPointer tids;
596 : : Datum *tids_datum;
597 : : int nposting;
598 : :
1506 pg@bowt.ie 599 :UBC 0 : tids = BTreeTupleGetPosting(itup);
600 : 0 : nposting = BTreeTupleGetNPosting(itup);
601 : 0 : tids_datum = (Datum *) palloc(nposting * sizeof(Datum));
602 [ # # ]: 0 : for (int i = 0; i < nposting; i++)
603 : 0 : tids_datum[i] = ItemPointerGetDatum(&tids[i]);
653 peter@eisentraut.org 604 : 0 : values[j++] = PointerGetDatum(construct_array_builtin(tids_datum, nposting, TIDOID));
1506 pg@bowt.ie 605 : 0 : pfree(tids_datum);
606 : : }
607 : : else
1506 pg@bowt.ie 608 :CBC 3 : nulls[j++] = true;
609 : :
610 : : /* Build and return the result tuple */
611 : 3 : tuple = heap_form_tuple(uargs->tupd, values, nulls);
612 : :
2567 peter_e@gmx.net 613 : 3 : return HeapTupleGetDatum(tuple);
614 : : }
615 : :
616 : : /*-------------------------------------------------------
617 : : * bt_page_items()
618 : : *
619 : : * Get IndexTupleData set in a btree page
620 : : *
621 : : * Usage: SELECT * FROM bt_page_items('t1_pkey', 1);
622 : : *-------------------------------------------------------
623 : : */
624 : : static Datum
1181 peter@eisentraut.org 625 : 8 : bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version)
626 : : {
2590 noah@leadboat.com 627 : 8 : text *relname = PG_GETARG_TEXT_PP(0);
1181 peter@eisentraut.org 628 [ + + ]: 8 : int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1));
629 : : Datum result;
630 : : FuncCallContext *fctx;
631 : : MemoryContext mctx;
632 : : ua_page_items *uargs;
633 : :
6076 tgl@sss.pgh.pa.us 634 [ - + ]: 8 : if (!superuser())
6076 tgl@sss.pgh.pa.us 635 [ # # ]:UBC 0 : ereport(ERROR,
636 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
637 : : errmsg("must be superuser to use pageinspect functions")));
638 : :
6177 bruce@momjian.us 639 [ + + ]:CBC 8 : if (SRF_IS_FIRSTCALL())
640 : : {
641 : : RangeVar *relrv;
642 : : Relation rel;
643 : : Buffer buffer;
644 : : BTPageOpaque opaque;
645 : : TupleDesc tupleDesc;
646 : :
647 : 6 : fctx = SRF_FIRSTCALL_INIT();
648 : :
6076 tgl@sss.pgh.pa.us 649 : 6 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
650 : 6 : rel = relation_openrv(relrv, AccessShareLock);
651 : :
468 652 : 6 : bt_index_block_validate(rel, blkno);
653 : :
6076 654 : 2 : buffer = ReadBuffer(rel, blkno);
4153 655 : 2 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
656 : :
657 : : /*
658 : : * We copy the page into local storage to avoid holding pin on the
659 : : * buffer longer than we must, and possibly failing to release it at
660 : : * all if the calling query doesn't fetch all rows.
661 : : */
6076 662 : 2 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
663 : :
468 664 : 2 : uargs = palloc(sizeof(ua_page_items));
665 : :
6076 666 : 2 : uargs->page = palloc(BLCKSZ);
2916 kgrittn@postgresql.o 667 : 2 : memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ);
668 : :
4153 tgl@sss.pgh.pa.us 669 : 2 : UnlockReleaseBuffer(buffer);
6076 670 : 2 : relation_close(rel, AccessShareLock);
671 : :
672 : 2 : uargs->offset = FirstOffsetNumber;
673 : :
744 michael@paquier.xyz 674 : 2 : opaque = BTPageGetOpaque(uargs->page);
675 : :
1145 pg@bowt.ie 676 [ + - ]: 2 : if (!P_ISDELETED(opaque))
677 : 2 : fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
678 : : else
679 : : {
680 : : /* Don't interpret BTDeletedPageData as index tuples */
1145 pg@bowt.ie 681 [ # # ]:UBC 0 : elog(NOTICE, "page from block " INT64_FORMAT " is deleted", blkno);
682 : 0 : fctx->max_calls = 0;
683 : : }
1506 pg@bowt.ie 684 :CBC 2 : uargs->leafpage = P_ISLEAF(opaque);
685 : 2 : uargs->rightmost = P_RIGHTMOST(opaque);
686 : :
687 : : /* Build a tuple descriptor for our result type */
6076 tgl@sss.pgh.pa.us 688 [ - + ]: 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
6076 tgl@sss.pgh.pa.us 689 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
1506 pg@bowt.ie 690 :CBC 2 : tupleDesc = BlessTupleDesc(tupleDesc);
691 : :
692 : 2 : uargs->tupd = tupleDesc;
693 : :
6177 bruce@momjian.us 694 : 2 : fctx->user_fctx = uargs;
695 : :
696 : 2 : MemoryContextSwitchTo(mctx);
697 : : }
698 : :
699 : 4 : fctx = SRF_PERCALL_SETUP();
700 : 4 : uargs = fctx->user_fctx;
701 : :
702 [ + + ]: 4 : if (fctx->call_cntr < fctx->max_calls)
703 : : {
1318 peter@eisentraut.org 704 : 2 : result = bt_page_print_tuples(uargs);
2567 peter_e@gmx.net 705 : 2 : uargs->offset++;
6177 bruce@momjian.us 706 : 2 : SRF_RETURN_NEXT(fctx, result);
707 : : }
708 : :
1490 tgl@sss.pgh.pa.us 709 : 2 : SRF_RETURN_DONE(fctx);
710 : : }
711 : :
712 : : Datum
1181 peter@eisentraut.org 713 : 6 : bt_page_items_1_9(PG_FUNCTION_ARGS)
714 : : {
715 : 6 : return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_9);
716 : : }
717 : :
718 : : /* entry point for old extension version */
719 : : Datum
720 : 2 : bt_page_items(PG_FUNCTION_ARGS)
721 : : {
722 : 2 : return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_8);
723 : : }
724 : :
725 : : /*-------------------------------------------------------
726 : : * bt_page_items_bytea()
727 : : *
728 : : * Get IndexTupleData set in a btree page
729 : : *
730 : : * Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1));
731 : : *-------------------------------------------------------
732 : : */
733 : :
734 : : Datum
2567 peter_e@gmx.net 735 : 9 : bt_page_items_bytea(PG_FUNCTION_ARGS)
736 : : {
737 : 9 : bytea *raw_page = PG_GETARG_BYTEA_P(0);
738 : : Datum result;
739 : : FuncCallContext *fctx;
740 : : ua_page_items *uargs;
741 : :
742 [ - + ]: 9 : if (!superuser())
2567 peter_e@gmx.net 743 [ # # ]:UBC 0 : ereport(ERROR,
744 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
745 : : errmsg("must be superuser to use raw page functions")));
746 : :
2567 peter_e@gmx.net 747 [ + + ]:CBC 9 : if (SRF_IS_FIRSTCALL())
748 : : {
749 : : BTPageOpaque opaque;
750 : : MemoryContext mctx;
751 : : TupleDesc tupleDesc;
752 : :
753 : 8 : fctx = SRF_FIRSTCALL_INIT();
754 : 8 : mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
755 : :
468 tgl@sss.pgh.pa.us 756 : 8 : uargs = palloc(sizeof(ua_page_items));
757 : :
760 michael@paquier.xyz 758 : 8 : uargs->page = get_page_from_raw(raw_page);
759 : :
731 760 [ + + ]: 7 : if (PageIsNew(uargs->page))
761 : : {
762 : 1 : MemoryContextSwitchTo(mctx);
763 : 1 : PG_RETURN_NULL();
764 : : }
765 : :
2567 peter_e@gmx.net 766 : 6 : uargs->offset = FirstOffsetNumber;
767 : :
768 : : /* verify the special space has the expected size */
749 michael@paquier.xyz 769 [ + + ]: 6 : if (PageGetSpecialSize(uargs->page) != MAXALIGN(sizeof(BTPageOpaqueData)))
770 [ + - ]: 2 : ereport(ERROR,
771 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
772 : : errmsg("input page is not a valid %s page", "btree"),
773 : : errdetail("Expected special size %d, got %d.",
774 : : (int) MAXALIGN(sizeof(BTPageOpaqueData)),
775 : : (int) PageGetSpecialSize(uargs->page))));
776 : :
744 777 : 4 : opaque = BTPageGetOpaque(uargs->page);
778 : :
2567 peter_e@gmx.net 779 [ + + ]: 4 : if (P_ISMETA(opaque))
780 [ + - ]: 2 : ereport(ERROR,
781 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
782 : : errmsg("block is a meta page")));
783 : :
749 michael@paquier.xyz 784 [ + - + + ]: 2 : if (P_ISLEAF(opaque) && opaque->btpo_level != 0)
785 [ + - ]: 1 : ereport(ERROR,
786 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
787 : : errmsg("block is not a valid btree leaf page")));
788 : :
2567 peter_e@gmx.net 789 [ - + ]: 1 : if (P_ISDELETED(opaque))
2567 peter_e@gmx.net 790 [ # # ]:UBC 0 : elog(NOTICE, "page is deleted");
791 : :
1145 pg@bowt.ie 792 [ + - ]:CBC 1 : if (!P_ISDELETED(opaque))
793 : 1 : fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
794 : : else
795 : : {
796 : : /* Don't interpret BTDeletedPageData as index tuples */
1145 pg@bowt.ie 797 [ # # ]:UBC 0 : elog(NOTICE, "page from block is deleted");
798 : 0 : fctx->max_calls = 0;
799 : : }
1506 pg@bowt.ie 800 :CBC 1 : uargs->leafpage = P_ISLEAF(opaque);
801 : 1 : uargs->rightmost = P_RIGHTMOST(opaque);
802 : :
803 : : /* Build a tuple descriptor for our result type */
2567 peter_e@gmx.net 804 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
2567 peter_e@gmx.net 805 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
1506 pg@bowt.ie 806 :CBC 1 : tupleDesc = BlessTupleDesc(tupleDesc);
807 : :
808 : 1 : uargs->tupd = tupleDesc;
809 : :
2567 peter_e@gmx.net 810 : 1 : fctx->user_fctx = uargs;
811 : :
812 : 1 : MemoryContextSwitchTo(mctx);
813 : : }
814 : :
815 : 2 : fctx = SRF_PERCALL_SETUP();
816 : 2 : uargs = fctx->user_fctx;
817 : :
818 [ + + ]: 2 : if (fctx->call_cntr < fctx->max_calls)
819 : : {
1318 peter@eisentraut.org 820 : 1 : result = bt_page_print_tuples(uargs);
2567 peter_e@gmx.net 821 : 1 : uargs->offset++;
822 : 1 : SRF_RETURN_NEXT(fctx, result);
823 : : }
824 : :
1490 tgl@sss.pgh.pa.us 825 : 1 : SRF_RETURN_DONE(fctx);
826 : : }
827 : :
828 : : /* Number of output arguments (columns) for bt_metap() */
829 : : #define BT_METAP_COLS_V1_8 9
830 : :
831 : : /* ------------------------------------------------
832 : : * bt_metap()
833 : : *
834 : : * Get a btree's meta-page information
835 : : *
836 : : * Usage: SELECT * FROM bt_metap('t1_pkey')
837 : : * ------------------------------------------------
838 : : */
839 : : Datum
6177 bruce@momjian.us 840 : 2 : bt_metap(PG_FUNCTION_ARGS)
841 : : {
2590 noah@leadboat.com 842 : 2 : text *relname = PG_GETARG_TEXT_PP(0);
843 : : Datum result;
844 : : Relation rel;
845 : : RangeVar *relrv;
846 : : BTMetaPageData *metad;
847 : : TupleDesc tupleDesc;
848 : : int j;
849 : : char *values[9];
850 : : Buffer buffer;
851 : : Page page;
852 : : HeapTuple tuple;
853 : :
6076 tgl@sss.pgh.pa.us 854 [ - + ]: 2 : if (!superuser())
6076 tgl@sss.pgh.pa.us 855 [ # # ]:UBC 0 : ereport(ERROR,
856 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
857 : : errmsg("must be superuser to use pageinspect functions")));
858 : :
6177 bruce@momjian.us 859 :CBC 2 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
860 : 2 : rel = relation_openrv(relrv, AccessShareLock);
861 : :
862 [ + - + + ]: 2 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
760 michael@paquier.xyz 863 [ + - ]: 1 : ereport(ERROR,
864 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
865 : : errmsg("\"%s\" is not a %s index",
866 : : RelationGetRelationName(rel), "btree")));
867 : :
868 : : /*
869 : : * Reject attempts to read non-local temporary relations; we would be
870 : : * likely to get wrong data since we have no visibility into the owning
871 : : * session's local buffers.
872 : : */
5493 tgl@sss.pgh.pa.us 873 [ - + - - ]: 1 : if (RELATION_IS_OTHER_TEMP(rel))
5493 tgl@sss.pgh.pa.us 874 [ # # ]:UBC 0 : ereport(ERROR,
875 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
876 : : errmsg("cannot access temporary tables of other sessions")));
877 : :
6177 bruce@momjian.us 878 :CBC 1 : buffer = ReadBuffer(rel, 0);
4153 tgl@sss.pgh.pa.us 879 : 1 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
880 : :
2916 kgrittn@postgresql.o 881 : 1 : page = BufferGetPage(buffer);
6076 tgl@sss.pgh.pa.us 882 : 1 : metad = BTPageGetMeta(page);
883 : :
884 : : /* Build a tuple descriptor for our result type */
885 [ - + ]: 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
6076 tgl@sss.pgh.pa.us 886 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
887 : :
888 : : /*
889 : : * We need a kluge here to detect API versions prior to 1.8. Earlier
890 : : * versions incorrectly used int4 for certain columns.
891 : : *
892 : : * There is no way to reliably avoid the problems created by the old
893 : : * function definition at this point, so insist that the user update the
894 : : * extension.
895 : : */
1499 pg@bowt.ie 896 [ - + ]:CBC 1 : if (tupleDesc->natts < BT_METAP_COLS_V1_8)
1499 pg@bowt.ie 897 [ # # ]:UBC 0 : ereport(ERROR,
898 : : (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
899 : : errmsg("function has wrong number of declared columns"),
900 : : errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version.")));
901 : :
6076 tgl@sss.pgh.pa.us 902 :CBC 1 : j = 0;
3751 peter_e@gmx.net 903 : 1 : values[j++] = psprintf("%d", metad->btm_magic);
904 : 1 : values[j++] = psprintf("%d", metad->btm_version);
1499 pg@bowt.ie 905 : 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_root);
906 : 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_level);
907 : 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastroot);
908 : 1 : values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastlevel);
909 : :
910 : : /*
911 : : * Get values of extended metadata if available, use default values
912 : : * otherwise. Note that we rely on the assumption that btm_allequalimage
913 : : * is initialized to zero with indexes that were built on versions prior
914 : : * to Postgres 13 (just like _bt_metaversion()).
915 : : */
1852 916 [ + - ]: 1 : if (metad->btm_version >= BTREE_NOVAC_VERSION)
917 : : {
1145 918 : 2 : values[j++] = psprintf(INT64_FORMAT,
919 : 1 : (int64) metad->btm_last_cleanup_num_delpages);
2156 tgl@sss.pgh.pa.us 920 : 1 : values[j++] = psprintf("%f", metad->btm_last_cleanup_num_heap_tuples);
1506 pg@bowt.ie 921 [ + - ]: 1 : values[j++] = metad->btm_allequalimage ? "t" : "f";
922 : : }
923 : : else
924 : : {
2201 teodor@sigaev.ru 925 :UBC 0 : values[j++] = "0";
926 : 0 : values[j++] = "-1";
1506 pg@bowt.ie 927 : 0 : values[j++] = "f";
928 : : }
929 : :
6076 tgl@sss.pgh.pa.us 930 :CBC 1 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
931 : : values);
932 : :
933 : 1 : result = HeapTupleGetDatum(tuple);
934 : :
4153 935 : 1 : UnlockReleaseBuffer(buffer);
6177 bruce@momjian.us 936 : 1 : relation_close(rel, AccessShareLock);
937 : :
938 : 1 : PG_RETURN_DATUM(result);
939 : : }
|