Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * misc.c
4 : : *
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/utils/adt/misc.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include <sys/file.h>
18 : : #include <sys/stat.h>
19 : : #include <dirent.h>
20 : : #include <fcntl.h>
21 : : #include <math.h>
22 : : #include <unistd.h>
23 : :
24 : : #include "access/htup_details.h"
25 : : #include "access/sysattr.h"
26 : : #include "access/table.h"
27 : : #include "catalog/pg_tablespace.h"
28 : : #include "catalog/pg_type.h"
29 : : #include "catalog/system_fk_info.h"
30 : : #include "commands/tablespace.h"
31 : : #include "common/keywords.h"
32 : : #include "funcapi.h"
33 : : #include "miscadmin.h"
34 : : #include "nodes/miscnodes.h"
35 : : #include "parser/parse_type.h"
36 : : #include "parser/scansup.h"
37 : : #include "pgstat.h"
38 : : #include "postmaster/syslogger.h"
39 : : #include "rewrite/rewriteHandler.h"
40 : : #include "storage/fd.h"
41 : : #include "storage/latch.h"
42 : : #include "tcop/tcopprot.h"
43 : : #include "utils/builtins.h"
44 : : #include "utils/fmgroids.h"
45 : : #include "utils/lsyscache.h"
46 : : #include "utils/ruleutils.h"
47 : : #include "utils/syscache.h"
48 : : #include "utils/timestamp.h"
49 : : #include "utils/tuplestore.h"
50 : : #include "utils/wait_event.h"
51 : :
52 : :
53 : : /*
54 : : * structure to cache metadata needed in pg_input_is_valid_common
55 : : */
56 : : typedef struct ValidIOData
57 : : {
58 : : Oid typoid;
59 : : int32 typmod;
60 : : bool typname_constant;
61 : : Oid typiofunc;
62 : : Oid typioparam;
63 : : FmgrInfo inputproc;
64 : : } ValidIOData;
65 : :
66 : : static bool pg_input_is_valid_common(FunctionCallInfo fcinfo,
67 : : text *txt, text *typname,
68 : : ErrorSaveContext *escontext);
69 : :
70 : :
71 : : /*
72 : : * Common subroutine for num_nulls() and num_nonnulls().
73 : : * Returns true if successful, false if function should return NULL.
74 : : * If successful, total argument count and number of nulls are
75 : : * returned into *nargs and *nulls.
76 : : */
77 : : static bool
3743 tgl@sss.pgh.pa.us 78 :CBC 98 : count_nulls(FunctionCallInfo fcinfo,
79 : : int32 *nargs, int32 *nulls)
80 : : {
81 : 98 : int32 count = 0;
82 : : int i;
83 : :
84 : : /* Did we get a VARIADIC array argument, or separate arguments? */
85 [ + + ]: 98 : if (get_fn_expr_variadic(fcinfo->flinfo))
86 : : {
87 : : ArrayType *arr;
88 : : int ndims,
89 : : nitems,
90 : : *dims;
91 : : uint8 *bitmap;
92 : :
93 [ - + ]: 48 : Assert(PG_NARGS() == 1);
94 : :
95 : : /*
96 : : * If we get a null as VARIADIC array argument, we can't say anything
97 : : * useful about the number of elements, so return NULL. This behavior
98 : : * is consistent with other variadic functions - see concat_internal.
99 : : */
100 [ + + ]: 48 : if (PG_ARGISNULL(0))
101 : 10 : return false;
102 : :
103 : : /*
104 : : * Non-null argument had better be an array. We assume that any call
105 : : * context that could let get_fn_expr_variadic return true will have
106 : : * checked that a VARIADIC-labeled parameter actually is an array. So
107 : : * it should be okay to just Assert that it's an array rather than
108 : : * doing a full-fledged error check.
109 : : */
110 [ - + ]: 38 : Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 0))));
111 : :
112 : : /* OK, safe to fetch the array value */
113 : 38 : arr = PG_GETARG_ARRAYTYPE_P(0);
114 : :
115 : : /* Count the array elements */
116 : 38 : ndims = ARR_NDIM(arr);
117 : 38 : dims = ARR_DIMS(arr);
118 : 38 : nitems = ArrayGetNItems(ndims, dims);
119 : :
120 : : /* Count those that are NULL */
121 [ + + ]: 38 : bitmap = ARR_NULLBITMAP(arr);
122 [ + + ]: 38 : if (bitmap)
123 : : {
124 : 18 : int bitmask = 1;
125 : :
126 [ + + ]: 858 : for (i = 0; i < nitems; i++)
127 : : {
128 [ + + ]: 840 : if ((*bitmap & bitmask) == 0)
129 : 18 : count++;
130 : :
131 : 840 : bitmask <<= 1;
132 [ + + ]: 840 : if (bitmask == 0x100)
133 : : {
134 : 96 : bitmap++;
135 : 96 : bitmask = 1;
136 : : }
137 : : }
138 : : }
139 : :
140 : 38 : *nargs = nitems;
141 : 38 : *nulls = count;
142 : : }
143 : : else
144 : : {
145 : : /* Separate arguments, so just count 'em */
146 [ + + ]: 170 : for (i = 0; i < PG_NARGS(); i++)
147 : : {
148 [ + + ]: 120 : if (PG_ARGISNULL(i))
149 : 70 : count++;
150 : : }
151 : :
152 : 50 : *nargs = PG_NARGS();
153 : 50 : *nulls = count;
154 : : }
155 : :
156 : 88 : return true;
157 : : }
158 : :
159 : : /*
160 : : * num_nulls()
161 : : * Count the number of NULL arguments
162 : : */
163 : : Datum
164 : 49 : pg_num_nulls(PG_FUNCTION_ARGS)
165 : : {
166 : : int32 nargs,
167 : : nulls;
168 : :
169 [ + + ]: 49 : if (!count_nulls(fcinfo, &nargs, &nulls))
170 : 5 : PG_RETURN_NULL();
171 : :
172 : 44 : PG_RETURN_INT32(nulls);
173 : : }
174 : :
175 : : /*
176 : : * num_nonnulls()
177 : : * Count the number of non-NULL arguments
178 : : */
179 : : Datum
180 : 49 : pg_num_nonnulls(PG_FUNCTION_ARGS)
181 : : {
182 : : int32 nargs,
183 : : nulls;
184 : :
185 [ + + ]: 49 : if (!count_nulls(fcinfo, &nargs, &nulls))
186 : 5 : PG_RETURN_NULL();
187 : :
188 : 44 : PG_RETURN_INT32(nargs - nulls);
189 : : }
190 : :
191 : : /*
192 : : * error_on_null()
193 : : * Check if the input is the NULL value
194 : : */
195 : : Datum
195 michael@paquier.xyz 196 :GNC 28 : pg_error_on_null(PG_FUNCTION_ARGS)
197 : : {
198 [ + + ]: 28 : if (PG_ARGISNULL(0))
199 [ + - ]: 8 : ereport(ERROR,
200 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
201 : : errmsg("null value not allowed")));
202 : :
203 : 20 : PG_RETURN_DATUM(PG_GETARG_DATUM(0));
204 : : }
205 : :
206 : : /*
207 : : * current_database()
208 : : * Expose the current database to the user
209 : : */
210 : : Datum
8659 bruce@momjian.us 211 :CBC 7377 : current_database(PG_FUNCTION_ARGS)
212 : : {
213 : : Name db;
214 : :
215 : 7377 : db = (Name) palloc(NAMEDATALEN);
216 : :
8348 peter_e@gmx.net 217 : 7377 : namestrcpy(db, get_database_name(MyDatabaseId));
8659 bruce@momjian.us 218 : 7377 : PG_RETURN_NAME(db);
219 : : }
220 : :
221 : :
222 : : /*
223 : : * current_query()
224 : : * Expose the current query to the user (useful in stored procedures)
225 : : * We might want to use ActivePortal->sourceText someday.
226 : : */
227 : : Datum
6605 bruce@momjian.us 228 :UBC 0 : current_query(PG_FUNCTION_ARGS)
229 : : {
230 : : /* there is no easy way to access the more concise 'query_string' */
6326 231 [ # # ]: 0 : if (debug_query_string)
232 : 0 : PG_RETURN_TEXT_P(cstring_to_text(debug_query_string));
233 : : else
234 : 0 : PG_RETURN_NULL();
235 : : }
236 : :
237 : : /* Function to find out which databases make use of a tablespace */
238 : :
239 : : Datum
7919 bruce@momjian.us 240 :CBC 4 : pg_tablespace_databases(PG_FUNCTION_ARGS)
241 : : {
2241 tgl@sss.pgh.pa.us 242 : 4 : Oid tablespaceOid = PG_GETARG_OID(0);
243 : 4 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
244 : : char *location;
245 : : DIR *dirdesc;
246 : : struct dirent *de;
247 : :
1295 michael@paquier.xyz 248 : 4 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
249 : :
2241 tgl@sss.pgh.pa.us 250 [ - + ]: 4 : if (tablespaceOid == GLOBALTABLESPACE_OID)
251 : : {
2241 tgl@sss.pgh.pa.us 252 [ # # ]:UBC 0 : ereport(WARNING,
253 : : (errmsg("global tablespace never has databases")));
254 : : /* return empty tuplestore */
255 : 0 : return (Datum) 0;
256 : : }
257 : :
2241 tgl@sss.pgh.pa.us 258 [ + - ]:CBC 4 : if (tablespaceOid == DEFAULTTABLESPACE_OID)
1337 drowley@postgresql.o 259 : 4 : location = "base";
260 : : else
609 michael@paquier.xyz 261 :UBC 0 : location = psprintf("%s/%u/%s", PG_TBLSPC_DIR, tablespaceOid,
262 : : TABLESPACE_VERSION_DIRECTORY);
263 : :
2241 tgl@sss.pgh.pa.us 264 :CBC 4 : dirdesc = AllocateDir(location);
265 : :
266 [ - + ]: 4 : if (!dirdesc)
267 : : {
268 : : /* the only expected error is ENOENT */
2241 tgl@sss.pgh.pa.us 269 [ # # ]:UBC 0 : if (errno != ENOENT)
270 [ # # ]: 0 : ereport(ERROR,
271 : : (errcode_for_file_access(),
272 : : errmsg("could not open directory \"%s\": %m",
273 : : location)));
274 [ # # ]: 0 : ereport(WARNING,
275 : : (errmsg("%u is not a tablespace OID", tablespaceOid)));
276 : : /* return empty tuplestore */
277 : 0 : return (Datum) 0;
278 : : }
279 : :
2241 tgl@sss.pgh.pa.us 280 [ + + ]:CBC 35 : while ((de = ReadDir(dirdesc, location)) != NULL)
281 : : {
7919 bruce@momjian.us 282 : 31 : Oid datOid = atooid(de->d_name);
283 : : char *subdir;
284 : : bool isempty;
285 : : Datum values[1];
286 : : bool nulls[1];
287 : :
288 : : /* this test skips . and .., but is awfully weak */
7977 mail@joeconway.com 289 [ + + ]: 31 : if (!datOid)
290 : 12 : continue;
291 : :
292 : : /* if database subdir is empty, don't report tablespace as used */
293 : :
2241 tgl@sss.pgh.pa.us 294 : 19 : subdir = psprintf("%s/%s", location, de->d_name);
3074 295 : 19 : isempty = directory_is_empty(subdir);
7625 296 : 19 : pfree(subdir);
297 : :
3074 298 [ - + ]: 19 : if (isempty)
7945 tgl@sss.pgh.pa.us 299 :UBC 0 : continue; /* indeed, nothing in it */
300 : :
2241 tgl@sss.pgh.pa.us 301 :CBC 19 : values[0] = ObjectIdGetDatum(datOid);
302 : 19 : nulls[0] = false;
303 : :
1520 michael@paquier.xyz 304 : 19 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
305 : : values, nulls);
306 : : }
307 : :
2241 tgl@sss.pgh.pa.us 308 : 4 : FreeDir(dirdesc);
309 : 4 : return (Datum) 0;
310 : : }
311 : :
312 : :
313 : : /*
314 : : * pg_tablespace_location - get location for a tablespace
315 : : */
316 : : Datum
5263 magnus@hagander.net 317 : 165 : pg_tablespace_location(PG_FUNCTION_ARGS)
318 : : {
5077 bruce@momjian.us 319 : 165 : Oid tablespaceOid = PG_GETARG_OID(0);
320 : : char *tablespaceLoc;
321 : :
322 : : /* Get LOCATION string from its OID */
174 alvherre@kurilemu.de 323 :GNC 165 : tablespaceLoc = get_tablespace_location(tablespaceOid);
324 : :
325 : 165 : PG_RETURN_TEXT_P(cstring_to_text(tablespaceLoc));
326 : : }
327 : :
328 : : /*
329 : : * pg_sleep - delay for N seconds
330 : : */
331 : : Datum
7419 tgl@sss.pgh.pa.us 332 :CBC 128 : pg_sleep(PG_FUNCTION_ARGS)
333 : : {
334 : 128 : float8 secs = PG_GETARG_FLOAT8(0);
335 : : int64 usecs;
336 : : TimestampTz endtime;
337 : :
338 : : /*
339 : : * Convert the delay to int64 microseconds, rounding up any fraction, and
340 : : * silently limiting it to PG_INT64_MAX/2 microseconds (about 150K years)
341 : : * to ensure the computation of endtime won't overflow. Historically
342 : : * we've treated NaN as "no wait", not an error, so keep that behavior.
343 : : */
222 tgl@sss.pgh.pa.us 344 [ + - - + ]:GNC 128 : if (isnan(secs) || secs <= 0.0)
222 tgl@sss.pgh.pa.us 345 :UNC 0 : PG_RETURN_VOID();
222 tgl@sss.pgh.pa.us 346 :GNC 128 : secs *= USECS_PER_SEC; /* we assume overflow will produce +Inf */
347 : 128 : secs = ceil(secs); /* round up any fractional microsecond */
348 [ + - ]: 128 : usecs = (int64) Min(secs, (float8) (PG_INT64_MAX / 2));
349 : :
350 : : /*
351 : : * We sleep using WaitLatch, to ensure that we'll wake up promptly if an
352 : : * important signal (such as SIGALRM or SIGINT) arrives. Because
353 : : * WaitLatch's upper limit of delay is INT_MAX milliseconds, and the user
354 : : * might ask for more than that, we sleep for at most 10 minutes and then
355 : : * loop.
356 : : *
357 : : * By computing the intended stop time initially, we avoid accumulation of
358 : : * extra delay across multiple sleeps. This also ensures we won't delay
359 : : * less than the specified time when WaitLatch is terminated early by a
360 : : * non-query-canceling signal such as SIGHUP.
361 : : */
362 : 128 : endtime = GetCurrentTimestamp() + usecs;
363 : :
364 : : for (;;)
7419 tgl@sss.pgh.pa.us 365 :CBC 129 : {
366 : : TimestampTz delay;
367 : : long delay_ms;
368 : :
369 [ + + ]: 257 : CHECK_FOR_INTERRUPTS();
370 : :
222 tgl@sss.pgh.pa.us 371 :GNC 244 : delay = endtime - GetCurrentTimestamp();
372 [ - + ]: 244 : if (delay >= 600 * USECS_PER_SEC)
4707 tgl@sss.pgh.pa.us 373 :UBC 0 : delay_ms = 600000;
222 tgl@sss.pgh.pa.us 374 [ + + ]:GNC 244 : else if (delay > 0)
375 : 129 : delay_ms = (long) ((delay + 999) / 1000);
376 : : else
7419 tgl@sss.pgh.pa.us 377 :CBC 115 : break;
378 : :
4129 andres@anarazel.de 379 : 129 : (void) WaitLatch(MyLatch,
380 : : WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
381 : : delay_ms,
382 : : WAIT_EVENT_PG_SLEEP);
383 : 129 : ResetLatch(MyLatch);
384 : : }
385 : :
7419 tgl@sss.pgh.pa.us 386 : 115 : PG_RETURN_VOID();
387 : : }
388 : :
389 : : /* Function to return the list of grammar keywords */
390 : : Datum
6515 tgl@sss.pgh.pa.us 391 :UBC 0 : pg_get_keywords(PG_FUNCTION_ARGS)
392 : : {
393 : : FuncCallContext *funcctx;
394 : :
395 [ # # ]: 0 : if (SRF_IS_FIRSTCALL())
396 : : {
397 : : MemoryContext oldcontext;
398 : : TupleDesc tupdesc;
399 : :
400 : 0 : funcctx = SRF_FIRSTCALL_INIT();
401 : 0 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
402 : :
1231 michael@paquier.xyz 403 [ # # ]: 0 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
404 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
405 : 0 : funcctx->tuple_desc = tupdesc;
6515 tgl@sss.pgh.pa.us 406 : 0 : funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
407 : :
408 : 0 : MemoryContextSwitchTo(oldcontext);
409 : : }
410 : :
411 : 0 : funcctx = SRF_PERCALL_SETUP();
412 : :
2676 413 [ # # ]: 0 : if (funcctx->call_cntr < ScanKeywords.num_keywords)
414 : : {
415 : : char *values[5];
416 : : HeapTuple tuple;
417 : :
418 : : /* cast-away-const is ugly but alternatives aren't much better */
419 : 0 : values[0] = unconstify(char *,
420 : : GetScanKeyword(funcctx->call_cntr,
421 : : &ScanKeywords));
422 : :
423 [ # # # # : 0 : switch (ScanKeywordCategories[funcctx->call_cntr])
# ]
424 : : {
6515 425 : 0 : case UNRESERVED_KEYWORD:
426 : 0 : values[1] = "U";
2055 427 : 0 : values[3] = _("unreserved");
6515 428 : 0 : break;
429 : 0 : case COL_NAME_KEYWORD:
430 : 0 : values[1] = "C";
2055 431 : 0 : values[3] = _("unreserved (cannot be function or type name)");
6515 432 : 0 : break;
433 : 0 : case TYPE_FUNC_NAME_KEYWORD:
434 : 0 : values[1] = "T";
2055 435 : 0 : values[3] = _("reserved (can be function or type name)");
6515 436 : 0 : break;
437 : 0 : case RESERVED_KEYWORD:
438 : 0 : values[1] = "R";
2055 439 : 0 : values[3] = _("reserved");
6515 440 : 0 : break;
441 : 0 : default: /* shouldn't be possible */
442 : 0 : values[1] = NULL;
2055 443 : 0 : values[3] = NULL;
6515 444 : 0 : break;
445 : : }
446 : :
2055 447 [ # # ]: 0 : if (ScanKeywordBareLabel[funcctx->call_cntr])
448 : : {
449 : 0 : values[2] = "true";
450 : 0 : values[4] = _("can be bare label");
451 : : }
452 : : else
453 : : {
454 : 0 : values[2] = "false";
455 : 0 : values[4] = _("requires AS");
456 : : }
457 : :
6515 458 : 0 : tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
459 : :
460 : 0 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
461 : : }
462 : :
463 : 0 : SRF_RETURN_DONE(funcctx);
464 : : }
465 : :
466 : :
467 : : /* Function to return the list of catalog foreign key relationships */
468 : : Datum
1918 tgl@sss.pgh.pa.us 469 :CBC 956 : pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS)
470 : : {
471 : : FuncCallContext *funcctx;
472 : : FmgrInfo *arrayinp;
473 : :
474 [ + + ]: 956 : if (SRF_IS_FIRSTCALL())
475 : : {
476 : : MemoryContext oldcontext;
477 : : TupleDesc tupdesc;
478 : :
479 : 4 : funcctx = SRF_FIRSTCALL_INIT();
480 : 4 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
481 : :
1231 michael@paquier.xyz 482 [ - + ]: 4 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1231 michael@paquier.xyz 483 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
1918 tgl@sss.pgh.pa.us 484 :CBC 4 : funcctx->tuple_desc = BlessTupleDesc(tupdesc);
485 : :
486 : : /*
487 : : * We use array_in to convert the C strings in sys_fk_relationships[]
488 : : * to text arrays. But we cannot use DirectFunctionCallN to call
489 : : * array_in, and it wouldn't be very efficient if we could. Fill an
490 : : * FmgrInfo to use for the call.
491 : : */
146 michael@paquier.xyz 492 :GNC 4 : arrayinp = palloc_object(FmgrInfo);
1918 tgl@sss.pgh.pa.us 493 :CBC 4 : fmgr_info(F_ARRAY_IN, arrayinp);
494 : 4 : funcctx->user_fctx = arrayinp;
495 : :
496 : 4 : MemoryContextSwitchTo(oldcontext);
497 : : }
498 : :
499 : 956 : funcctx = SRF_PERCALL_SETUP();
500 : 956 : arrayinp = (FmgrInfo *) funcctx->user_fctx;
501 : :
502 [ + + ]: 956 : if (funcctx->call_cntr < lengthof(sys_fk_relationships))
503 : : {
504 : 952 : const SysFKRelationship *fkrel = &sys_fk_relationships[funcctx->call_cntr];
505 : : Datum values[6];
506 : : bool nulls[6];
507 : : HeapTuple tuple;
508 : :
509 : 952 : memset(nulls, false, sizeof(nulls));
510 : :
511 : 952 : values[0] = ObjectIdGetDatum(fkrel->fk_table);
512 : 952 : values[1] = FunctionCall3(arrayinp,
513 : : CStringGetDatum(fkrel->fk_columns),
514 : : ObjectIdGetDatum(TEXTOID),
515 : : Int32GetDatum(-1));
516 : 952 : values[2] = ObjectIdGetDatum(fkrel->pk_table);
517 : 952 : values[3] = FunctionCall3(arrayinp,
518 : : CStringGetDatum(fkrel->pk_columns),
519 : : ObjectIdGetDatum(TEXTOID),
520 : : Int32GetDatum(-1));
521 : 952 : values[4] = BoolGetDatum(fkrel->is_array);
522 : 952 : values[5] = BoolGetDatum(fkrel->is_opt);
523 : :
524 : 952 : tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
525 : :
526 : 952 : SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
527 : : }
528 : :
529 : 4 : SRF_RETURN_DONE(funcctx);
530 : : }
531 : :
532 : :
533 : : /*
534 : : * Return the type of the argument.
535 : : */
536 : : Datum
6392 537 : 934 : pg_typeof(PG_FUNCTION_ARGS)
538 : : {
539 : 934 : PG_RETURN_OID(get_fn_expr_argtype(fcinfo->flinfo, 0));
540 : : }
541 : :
542 : :
543 : : /*
544 : : * Return the base type of the argument.
545 : : * If the given type is a domain, return its base type;
546 : : * otherwise return the type's own OID.
547 : : * Return NULL if the type OID doesn't exist or points to a
548 : : * non-existent base type.
549 : : *
550 : : * This is a SQL-callable version of getBaseType(). Unlike that function,
551 : : * we don't want to fail for a bogus type OID; this is helpful to keep race
552 : : * conditions from turning into query failures when scanning the catalogs.
553 : : * Hence we need our own implementation.
554 : : */
555 : : Datum
766 556 : 12 : pg_basetype(PG_FUNCTION_ARGS)
557 : : {
558 : 12 : Oid typid = PG_GETARG_OID(0);
559 : :
560 : : /*
561 : : * We loop to find the bottom base type in a stack of domains.
562 : : */
563 : : for (;;)
564 : 12 : {
565 : : HeapTuple tup;
566 : : Form_pg_type typTup;
567 : :
568 : 24 : tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
569 [ + + ]: 24 : if (!HeapTupleIsValid(tup))
570 : 4 : PG_RETURN_NULL(); /* return NULL for bogus OID */
571 : 20 : typTup = (Form_pg_type) GETSTRUCT(tup);
572 [ + + ]: 20 : if (typTup->typtype != TYPTYPE_DOMAIN)
573 : : {
574 : : /* Not a domain, so done */
575 : 8 : ReleaseSysCache(tup);
576 : 8 : break;
577 : : }
578 : :
579 : 12 : typid = typTup->typbasetype;
580 : 12 : ReleaseSysCache(tup);
581 : : }
582 : :
583 : 8 : PG_RETURN_OID(typid);
584 : : }
585 : :
586 : :
587 : : /*
588 : : * Implementation of the COLLATE FOR expression; returns the collation
589 : : * of the argument.
590 : : */
591 : : Datum
5177 peter_e@gmx.net 592 : 20 : pg_collation_for(PG_FUNCTION_ARGS)
593 : : {
594 : : Oid typeid;
595 : : Oid collid;
596 : :
597 : 20 : typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
598 [ - + ]: 20 : if (!typeid)
5177 peter_e@gmx.net 599 :UBC 0 : PG_RETURN_NULL();
5177 peter_e@gmx.net 600 [ + + + + ]:CBC 20 : if (!type_is_collatable(typeid) && typeid != UNKNOWNOID)
601 [ + - ]: 4 : ereport(ERROR,
602 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
603 : : errmsg("collations are not supported by type %s",
604 : : format_type_be(typeid))));
605 : :
606 : 16 : collid = PG_GET_COLLATION();
607 [ + + ]: 16 : if (!collid)
608 : 4 : PG_RETURN_NULL();
609 : 12 : PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
610 : : }
611 : :
612 : :
613 : : /*
614 : : * pg_relation_is_updatable - determine which update events the specified
615 : : * relation supports.
616 : : *
617 : : * This relies on relation_is_updatable() in rewriteHandler.c, which see
618 : : * for additional information.
619 : : */
620 : : Datum
4710 tgl@sss.pgh.pa.us 621 : 596 : pg_relation_is_updatable(PG_FUNCTION_ARGS)
622 : : {
623 : 596 : Oid reloid = PG_GETARG_OID(0);
624 : 596 : bool include_triggers = PG_GETARG_BOOL(1);
625 : :
2357 626 : 596 : PG_RETURN_INT32(relation_is_updatable(reloid, NIL, include_triggers, NULL));
627 : : }
628 : :
629 : : /*
630 : : * pg_column_is_updatable - determine whether a column is updatable
631 : : *
632 : : * This function encapsulates the decision about just what
633 : : * information_schema.columns.is_updatable actually means. It's not clear
634 : : * whether deletability of the column's relation should be required, so
635 : : * we want that decision in C code where we could change it without initdb.
636 : : */
637 : : Datum
4710 638 : 444 : pg_column_is_updatable(PG_FUNCTION_ARGS)
639 : : {
640 : 444 : Oid reloid = PG_GETARG_OID(0);
641 : 444 : AttrNumber attnum = PG_GETARG_INT16(1);
4582 rhaas@postgresql.org 642 : 444 : AttrNumber col = attnum - FirstLowInvalidHeapAttributeNumber;
4710 tgl@sss.pgh.pa.us 643 : 444 : bool include_triggers = PG_GETARG_BOOL(2);
644 : : int events;
645 : :
646 : : /* System columns are never updatable */
647 [ - + ]: 444 : if (attnum <= 0)
4710 tgl@sss.pgh.pa.us 648 :UBC 0 : PG_RETURN_BOOL(false);
649 : :
2357 tgl@sss.pgh.pa.us 650 :CBC 444 : events = relation_is_updatable(reloid, NIL, include_triggers,
651 : : bms_make_singleton(col));
652 : :
653 : : /* We require both updatability and deletability of the relation */
654 : : #define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
655 : :
4710 656 : 444 : PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS);
657 : : }
658 : :
659 : :
660 : : /*
661 : : * pg_input_is_valid - test whether string is valid input for datatype.
662 : : *
663 : : * Returns true if OK, false if not.
664 : : *
665 : : * This will only work usefully if the datatype's input function has been
666 : : * updated to return "soft" errors via errsave/ereturn.
667 : : */
668 : : Datum
1243 669 : 593 : pg_input_is_valid(PG_FUNCTION_ARGS)
670 : : {
671 : 593 : text *txt = PG_GETARG_TEXT_PP(0);
672 : 593 : text *typname = PG_GETARG_TEXT_PP(1);
673 : 593 : ErrorSaveContext escontext = {T_ErrorSaveContext};
674 : :
675 : 593 : PG_RETURN_BOOL(pg_input_is_valid_common(fcinfo, txt, typname,
676 : : &escontext));
677 : : }
678 : :
679 : : /*
680 : : * pg_input_error_info - test whether string is valid input for datatype.
681 : : *
682 : : * Returns NULL if OK, else the primary message, detail message, hint message
683 : : * and sql error code from the error.
684 : : *
685 : : * This will only work usefully if the datatype's input function has been
686 : : * updated to return "soft" errors via errsave/ereturn.
687 : : */
688 : : Datum
1162 michael@paquier.xyz 689 : 835 : pg_input_error_info(PG_FUNCTION_ARGS)
690 : : {
1243 tgl@sss.pgh.pa.us 691 : 835 : text *txt = PG_GETARG_TEXT_PP(0);
692 : 835 : text *typname = PG_GETARG_TEXT_PP(1);
693 : 835 : ErrorSaveContext escontext = {T_ErrorSaveContext};
694 : : TupleDesc tupdesc;
695 : : Datum values[4];
696 : : bool isnull[4];
697 : :
1162 michael@paquier.xyz 698 [ - + ]: 835 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1162 michael@paquier.xyz 699 [ # # ]:UBC 0 : elog(ERROR, "return type must be a row type");
700 : :
701 : : /* Enable details_wanted */
1243 tgl@sss.pgh.pa.us 702 :CBC 835 : escontext.details_wanted = true;
703 : :
704 [ + + ]: 835 : if (pg_input_is_valid_common(fcinfo, txt, typname,
705 : : &escontext))
1162 michael@paquier.xyz 706 : 19 : memset(isnull, true, sizeof(isnull));
707 : : else
708 : : {
709 : : char *sqlstate;
710 : :
711 [ - + ]: 796 : Assert(escontext.error_occurred);
712 [ - + ]: 796 : Assert(escontext.error_data != NULL);
713 [ - + ]: 796 : Assert(escontext.error_data->message != NULL);
714 : :
715 : 796 : memset(isnull, false, sizeof(isnull));
716 : :
717 : 796 : values[0] = CStringGetTextDatum(escontext.error_data->message);
718 : :
719 [ + + ]: 796 : if (escontext.error_data->detail != NULL)
720 : 356 : values[1] = CStringGetTextDatum(escontext.error_data->detail);
721 : : else
722 : 440 : isnull[1] = true;
723 : :
724 [ - + ]: 796 : if (escontext.error_data->hint != NULL)
1162 michael@paquier.xyz 725 :UBC 0 : values[2] = CStringGetTextDatum(escontext.error_data->hint);
726 : : else
1162 michael@paquier.xyz 727 :CBC 796 : isnull[2] = true;
728 : :
729 : 796 : sqlstate = unpack_sql_state(escontext.error_data->sqlerrcode);
730 : 796 : values[3] = CStringGetTextDatum(sqlstate);
731 : : }
732 : :
733 : 815 : return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
734 : : }
735 : :
736 : : /* Common subroutine for the above */
737 : : static bool
1243 tgl@sss.pgh.pa.us 738 : 1428 : pg_input_is_valid_common(FunctionCallInfo fcinfo,
739 : : text *txt, text *typname,
740 : : ErrorSaveContext *escontext)
741 : : {
742 : 1428 : char *str = text_to_cstring(txt);
743 : : ValidIOData *my_extra;
744 : : Datum converted;
745 : :
746 : : /*
747 : : * We arrange to look up the needed I/O info just once per series of
748 : : * calls, assuming the data type doesn't change underneath us.
749 : : */
750 : 1428 : my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra;
751 [ + + ]: 1428 : if (my_extra == NULL)
752 : : {
753 : 2728 : fcinfo->flinfo->fn_extra =
754 : 1364 : MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
755 : : sizeof(ValidIOData));
756 : 1364 : my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra;
757 : 1364 : my_extra->typoid = InvalidOid;
758 : : /* Detect whether typname argument is constant. */
759 : 1364 : my_extra->typname_constant = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
760 : : }
761 : :
762 : : /*
763 : : * If the typname argument is constant, we only need to parse it the first
764 : : * time through.
765 : : */
766 [ + + + + ]: 1428 : if (my_extra->typoid == InvalidOid || !my_extra->typname_constant)
767 : : {
768 : 1382 : char *typnamestr = text_to_cstring(typname);
769 : : Oid typoid;
770 : :
771 : : /* Parse type-name argument to obtain type OID and encoded typmod. */
1225 772 : 1382 : (void) parseTypeString(typnamestr, &typoid, &my_extra->typmod, NULL);
773 : :
774 : : /* Update type-specific info if typoid changed. */
1243 775 [ + + ]: 1382 : if (my_extra->typoid != typoid)
776 : : {
777 : 1372 : getTypeInputInfo(typoid,
778 : : &my_extra->typiofunc,
779 : : &my_extra->typioparam);
780 : 1372 : fmgr_info_cxt(my_extra->typiofunc, &my_extra->inputproc,
781 : 1372 : fcinfo->flinfo->fn_mcxt);
782 : 1372 : my_extra->typoid = typoid;
783 : : }
784 : : }
785 : :
786 : : /* Now we can try to perform the conversion. */
787 : 1428 : return InputFunctionCallSafe(&my_extra->inputproc,
788 : : str,
789 : : my_extra->typioparam,
790 : : my_extra->typmod,
791 : : (Node *) escontext,
792 : : &converted);
793 : : }
794 : :
795 : :
796 : : /*
797 : : * Is character a valid identifier start?
798 : : * Must match scan.l's {ident_start} character class.
799 : : */
800 : : static bool
3700 teodor@sigaev.ru 801 : 1801 : is_ident_start(unsigned char c)
802 : : {
803 : : /* Underscores and ASCII letters are OK */
804 [ - + ]: 1801 : if (c == '_')
3700 teodor@sigaev.ru 805 :UBC 0 : return true;
3700 teodor@sigaev.ru 806 [ + + - + :CBC 1801 : if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ + + + ]
807 : 1692 : return true;
808 : : /* Any high-bit-set character is OK (might be part of a multibyte char) */
3690 tgl@sss.pgh.pa.us 809 [ - + ]: 109 : if (IS_HIGHBIT_SET(c))
3700 teodor@sigaev.ru 810 :UBC 0 : return true;
3700 teodor@sigaev.ru 811 :CBC 109 : return false;
812 : : }
813 : :
814 : : /*
815 : : * Is character a valid identifier continuation?
816 : : * Must match scan.l's {ident_cont} character class.
817 : : */
818 : : static bool
819 : 1692 : is_ident_cont(unsigned char c)
820 : : {
821 : : /* Can be digit or dollar sign ... */
3690 tgl@sss.pgh.pa.us 822 [ + + + - : 1692 : if ((c >= '0' && c <= '9') || c == '$')
- + ]
3700 teodor@sigaev.ru 823 :UBC 0 : return true;
824 : : /* ... or an identifier start character */
3700 teodor@sigaev.ru 825 :CBC 1692 : return is_ident_start(c);
826 : : }
827 : :
828 : : /*
829 : : * parse_ident - parse a SQL qualified identifier into separate identifiers.
830 : : * When strict mode is active (second parameter), then any chars after
831 : : * the last identifier are disallowed.
832 : : */
833 : : Datum
834 : 83 : parse_ident(PG_FUNCTION_ARGS)
835 : : {
3690 tgl@sss.pgh.pa.us 836 : 83 : text *qualname = PG_GETARG_TEXT_PP(0);
837 : 83 : bool strict = PG_GETARG_BOOL(1);
838 : 83 : char *qualname_str = text_to_cstring(qualname);
839 : 83 : ArrayBuildState *astate = NULL;
840 : : char *nextp;
3700 teodor@sigaev.ru 841 : 83 : bool after_dot = false;
842 : :
843 : : /*
844 : : * The code below scribbles on qualname_str in some cases, so we should
845 : : * reconvert qualname if we need to show the original string in error
846 : : * messages.
847 : : */
848 : 83 : nextp = qualname_str;
849 : :
850 : : /* skip leading whitespace */
3268 tgl@sss.pgh.pa.us 851 [ + + ]: 105 : while (scanner_isspace(*nextp))
3700 teodor@sigaev.ru 852 : 22 : nextp++;
853 : :
854 : : for (;;)
855 : 75 : {
856 : : char *curname;
3690 tgl@sss.pgh.pa.us 857 : 158 : bool missing_ident = true;
858 : :
859 [ + + ]: 158 : if (*nextp == '"')
860 : : {
861 : : char *endp;
862 : :
3700 teodor@sigaev.ru 863 : 49 : curname = nextp + 1;
864 : : for (;;)
865 : : {
3690 tgl@sss.pgh.pa.us 866 : 49 : endp = strchr(nextp + 1, '"');
3700 teodor@sigaev.ru 867 [ - + ]: 49 : if (endp == NULL)
3700 teodor@sigaev.ru 868 [ # # ]:UBC 0 : ereport(ERROR,
869 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
870 : : errmsg("string is not a valid identifier: \"%s\"",
871 : : text_to_cstring(qualname)),
872 : : errdetail("String has unclosed double quotes.")));
3690 tgl@sss.pgh.pa.us 873 [ + - ]:CBC 49 : if (endp[1] != '"')
3700 teodor@sigaev.ru 874 : 49 : break;
3700 teodor@sigaev.ru 875 :UBC 0 : memmove(endp, endp + 1, strlen(endp));
876 : 0 : nextp = endp;
877 : : }
3700 teodor@sigaev.ru 878 :CBC 49 : nextp = endp + 1;
879 : 49 : *endp = '\0';
880 : :
881 [ - + ]: 49 : if (endp - curname == 0)
3700 teodor@sigaev.ru 882 [ # # ]:UBC 0 : ereport(ERROR,
883 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
884 : : errmsg("string is not a valid identifier: \"%s\"",
885 : : text_to_cstring(qualname)),
886 : : errdetail("Quoted identifier must not be empty.")));
887 : :
3700 teodor@sigaev.ru 888 :CBC 49 : astate = accumArrayResult(astate, CStringGetTextDatum(curname),
889 : : false, TEXTOID, CurrentMemoryContext);
890 : 49 : missing_ident = false;
891 : : }
3690 tgl@sss.pgh.pa.us 892 [ + + ]: 109 : else if (is_ident_start((unsigned char) *nextp))
893 : : {
894 : : char *downname;
895 : : int len;
896 : : text *part;
897 : :
898 : 77 : curname = nextp++;
899 [ + + ]: 1692 : while (is_ident_cont((unsigned char) *nextp))
900 : 1615 : nextp++;
901 : :
902 : 77 : len = nextp - curname;
903 : :
904 : : /*
905 : : * We don't implicitly truncate identifiers. This is useful for
906 : : * allowing the user to check for specific parts of the identifier
907 : : * being too long. It's easy enough for the user to get the
908 : : * truncated names by casting our output to name[].
909 : : */
910 : 77 : downname = downcase_identifier(curname, len, false, false);
911 : 77 : part = cstring_to_text_with_len(downname, len);
912 : 77 : astate = accumArrayResult(astate, PointerGetDatum(part), false,
913 : : TEXTOID, CurrentMemoryContext);
914 : 77 : missing_ident = false;
915 : : }
916 : :
3700 teodor@sigaev.ru 917 [ + + ]: 158 : if (missing_ident)
918 : : {
919 : : /* Different error messages based on where we failed. */
920 [ + + ]: 32 : if (*nextp == '.')
921 [ + - ]: 12 : ereport(ERROR,
922 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
923 : : errmsg("string is not a valid identifier: \"%s\"",
924 : : text_to_cstring(qualname)),
925 : : errdetail("No valid identifier before \".\".")));
926 [ + + ]: 20 : else if (after_dot)
927 [ + - ]: 8 : ereport(ERROR,
928 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
929 : : errmsg("string is not a valid identifier: \"%s\"",
930 : : text_to_cstring(qualname)),
931 : : errdetail("No valid identifier after \".\".")));
932 : : else
933 [ + - ]: 12 : ereport(ERROR,
934 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
935 : : errmsg("string is not a valid identifier: \"%s\"",
936 : : text_to_cstring(qualname))));
937 : : }
938 : :
3268 tgl@sss.pgh.pa.us 939 [ + + ]: 158 : while (scanner_isspace(*nextp))
3700 teodor@sigaev.ru 940 : 32 : nextp++;
941 : :
942 [ + + ]: 126 : if (*nextp == '.')
943 : : {
944 : 75 : after_dot = true;
945 : 75 : nextp++;
3268 tgl@sss.pgh.pa.us 946 [ + + ]: 99 : while (scanner_isspace(*nextp))
3700 teodor@sigaev.ru 947 : 24 : nextp++;
948 : : }
949 [ + + ]: 51 : else if (*nextp == '\0')
950 : : {
951 : 30 : break;
952 : : }
953 : : else
954 : : {
955 [ + + ]: 21 : if (strict)
956 [ + - ]: 16 : ereport(ERROR,
957 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
958 : : errmsg("string is not a valid identifier: \"%s\"",
959 : : text_to_cstring(qualname))));
960 : 5 : break;
961 : : }
962 : : }
963 : :
964 : 35 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
965 : : }
966 : :
967 : : /*
968 : : * pg_current_logfile
969 : : *
970 : : * Report current log file used by log collector by scanning current_logfiles.
971 : : */
972 : : Datum
3350 rhaas@postgresql.org 973 : 6 : pg_current_logfile(PG_FUNCTION_ARGS)
974 : : {
975 : : FILE *fd;
976 : : char lbuffer[MAXPGPATH];
977 : : char *logfmt;
978 : :
979 : : /* The log format parameter is optional */
980 [ + - - + ]: 6 : if (PG_NARGS() == 0 || PG_ARGISNULL(0))
3350 rhaas@postgresql.org 981 :UBC 0 : logfmt = NULL;
982 : : else
983 : : {
3350 rhaas@postgresql.org 984 :CBC 6 : logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0));
985 : :
1569 michael@paquier.xyz 986 [ + + ]: 6 : if (strcmp(logfmt, "stderr") != 0 &&
987 [ + + ]: 4 : strcmp(logfmt, "csvlog") != 0 &&
988 [ - + ]: 2 : strcmp(logfmt, "jsonlog") != 0)
3350 rhaas@postgresql.org 989 [ # # ]:UBC 0 : ereport(ERROR,
990 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
991 : : errmsg("log format \"%s\" is not supported", logfmt),
992 : : errhint("The supported log formats are \"stderr\", \"csvlog\", and \"jsonlog\".")));
993 : : }
994 : :
3350 rhaas@postgresql.org 995 :CBC 6 : fd = AllocateFile(LOG_METAINFO_DATAFILE, "r");
996 [ - + ]: 6 : if (fd == NULL)
997 : : {
3350 rhaas@postgresql.org 998 [ # # ]:UBC 0 : if (errno != ENOENT)
999 [ # # ]: 0 : ereport(ERROR,
1000 : : (errcode_for_file_access(),
1001 : : errmsg("could not read file \"%s\": %m",
1002 : : LOG_METAINFO_DATAFILE)));
1003 : 0 : PG_RETURN_NULL();
1004 : : }
1005 : :
1006 : : #ifdef WIN32
1007 : : /* syslogger.c writes CRLF line endings on Windows */
1008 : : _setmode(_fileno(fd), _O_TEXT);
1009 : : #endif
1010 : :
1011 : : /*
1012 : : * Read the file to gather current log filename(s) registered by the
1013 : : * syslogger.
1014 : : */
3350 rhaas@postgresql.org 1015 [ + - ]:CBC 12 : while (fgets(lbuffer, sizeof(lbuffer), fd) != NULL)
1016 : : {
1017 : : char *log_format;
1018 : : char *log_filepath;
1019 : : char *nlpos;
1020 : :
1021 : : /* Extract log format and log file path from the line. */
2126 tgl@sss.pgh.pa.us 1022 : 12 : log_format = lbuffer;
3350 rhaas@postgresql.org 1023 : 12 : log_filepath = strchr(lbuffer, ' ');
1024 [ - + ]: 12 : if (log_filepath == NULL)
1025 : : {
1026 : : /* Uh oh. No space found, so file content is corrupted. */
3350 rhaas@postgresql.org 1027 [ # # ]:UBC 0 : elog(ERROR,
1028 : : "missing space character in \"%s\"", LOG_METAINFO_DATAFILE);
1029 : : break;
1030 : : }
1031 : :
3350 rhaas@postgresql.org 1032 :CBC 12 : *log_filepath = '\0';
1033 : 12 : log_filepath++;
1034 : 12 : nlpos = strchr(log_filepath, '\n');
1035 [ - + ]: 12 : if (nlpos == NULL)
1036 : : {
1037 : : /* Uh oh. No newline found, so file content is corrupted. */
3350 rhaas@postgresql.org 1038 [ # # ]:UBC 0 : elog(ERROR,
1039 : : "missing newline character in \"%s\"", LOG_METAINFO_DATAFILE);
1040 : : break;
1041 : : }
3350 rhaas@postgresql.org 1042 :CBC 12 : *nlpos = '\0';
1043 : :
1044 [ + - + + ]: 12 : if (logfmt == NULL || strcmp(logfmt, log_format) == 0)
1045 : : {
1046 : 6 : FreeFile(fd);
1047 : 6 : PG_RETURN_TEXT_P(cstring_to_text(log_filepath));
1048 : : }
1049 : : }
1050 : :
1051 : : /* Close the current log filename file. */
3350 rhaas@postgresql.org 1052 :UBC 0 : FreeFile(fd);
1053 : :
1054 : 0 : PG_RETURN_NULL();
1055 : : }
1056 : :
1057 : : /*
1058 : : * Report current log file used by log collector (1 argument version)
1059 : : *
1060 : : * note: this wrapper is necessary to pass the sanity check in opr_sanity,
1061 : : * which checks that all built-in functions that share the implementing C
1062 : : * function take the same number of arguments
1063 : : */
1064 : : Datum
3350 rhaas@postgresql.org 1065 :CBC 6 : pg_current_logfile_1arg(PG_FUNCTION_ARGS)
1066 : : {
1067 : 6 : return pg_current_logfile(fcinfo);
1068 : : }
1069 : :
1070 : : /*
1071 : : * SQL wrapper around RelationGetReplicaIndex().
1072 : : */
1073 : : Datum
3330 peter_e@gmx.net 1074 : 421 : pg_get_replica_identity_index(PG_FUNCTION_ARGS)
1075 : : {
1076 : 421 : Oid reloid = PG_GETARG_OID(0);
1077 : : Oid idxoid;
1078 : : Relation rel;
1079 : :
2661 andres@anarazel.de 1080 : 421 : rel = table_open(reloid, AccessShareLock);
3330 peter_e@gmx.net 1081 : 421 : idxoid = RelationGetReplicaIndex(rel);
2661 andres@anarazel.de 1082 : 421 : table_close(rel, AccessShareLock);
1083 : :
3330 peter_e@gmx.net 1084 [ + + ]: 421 : if (OidIsValid(idxoid))
1085 : 212 : PG_RETURN_OID(idxoid);
1086 : : else
1087 : 209 : PG_RETURN_NULL();
1088 : : }
1089 : :
1090 : : /*
1091 : : * Transition function for the ANY_VALUE aggregate
1092 : : */
1093 : : Datum
1168 peter@eisentraut.org 1094 : 12 : any_value_transfn(PG_FUNCTION_ARGS)
1095 : : {
1096 : 12 : PG_RETURN_DATUM(PG_GETARG_DATUM(0));
1097 : : }
|