1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * misc.c |
4 | * |
5 | * |
6 | * Portions Copyright (c) 1996-2019, 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 <dirent.h> |
19 | #include <math.h> |
20 | #include <unistd.h> |
21 | |
22 | #include "access/sysattr.h" |
23 | #include "access/table.h" |
24 | #include "catalog/catalog.h" |
25 | #include "catalog/pg_tablespace.h" |
26 | #include "catalog/pg_type.h" |
27 | #include "commands/dbcommands.h" |
28 | #include "commands/tablespace.h" |
29 | #include "common/keywords.h" |
30 | #include "funcapi.h" |
31 | #include "miscadmin.h" |
32 | #include "pgstat.h" |
33 | #include "parser/scansup.h" |
34 | #include "postmaster/syslogger.h" |
35 | #include "rewrite/rewriteHandler.h" |
36 | #include "storage/fd.h" |
37 | #include "utils/lsyscache.h" |
38 | #include "utils/ruleutils.h" |
39 | #include "tcop/tcopprot.h" |
40 | #include "utils/builtins.h" |
41 | #include "utils/timestamp.h" |
42 | |
43 | |
44 | /* |
45 | * Common subroutine for num_nulls() and num_nonnulls(). |
46 | * Returns true if successful, false if function should return NULL. |
47 | * If successful, total argument count and number of nulls are |
48 | * returned into *nargs and *nulls. |
49 | */ |
50 | static bool |
51 | count_nulls(FunctionCallInfo fcinfo, |
52 | int32 *nargs, int32 *nulls) |
53 | { |
54 | int32 count = 0; |
55 | int i; |
56 | |
57 | /* Did we get a VARIADIC array argument, or separate arguments? */ |
58 | if (get_fn_expr_variadic(fcinfo->flinfo)) |
59 | { |
60 | ArrayType *arr; |
61 | int ndims, |
62 | nitems, |
63 | *dims; |
64 | bits8 *bitmap; |
65 | |
66 | Assert(PG_NARGS() == 1); |
67 | |
68 | /* |
69 | * If we get a null as VARIADIC array argument, we can't say anything |
70 | * useful about the number of elements, so return NULL. This behavior |
71 | * is consistent with other variadic functions - see concat_internal. |
72 | */ |
73 | if (PG_ARGISNULL(0)) |
74 | return false; |
75 | |
76 | /* |
77 | * Non-null argument had better be an array. We assume that any call |
78 | * context that could let get_fn_expr_variadic return true will have |
79 | * checked that a VARIADIC-labeled parameter actually is an array. So |
80 | * it should be okay to just Assert that it's an array rather than |
81 | * doing a full-fledged error check. |
82 | */ |
83 | Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 0)))); |
84 | |
85 | /* OK, safe to fetch the array value */ |
86 | arr = PG_GETARG_ARRAYTYPE_P(0); |
87 | |
88 | /* Count the array elements */ |
89 | ndims = ARR_NDIM(arr); |
90 | dims = ARR_DIMS(arr); |
91 | nitems = ArrayGetNItems(ndims, dims); |
92 | |
93 | /* Count those that are NULL */ |
94 | bitmap = ARR_NULLBITMAP(arr); |
95 | if (bitmap) |
96 | { |
97 | int bitmask = 1; |
98 | |
99 | for (i = 0; i < nitems; i++) |
100 | { |
101 | if ((*bitmap & bitmask) == 0) |
102 | count++; |
103 | |
104 | bitmask <<= 1; |
105 | if (bitmask == 0x100) |
106 | { |
107 | bitmap++; |
108 | bitmask = 1; |
109 | } |
110 | } |
111 | } |
112 | |
113 | *nargs = nitems; |
114 | *nulls = count; |
115 | } |
116 | else |
117 | { |
118 | /* Separate arguments, so just count 'em */ |
119 | for (i = 0; i < PG_NARGS(); i++) |
120 | { |
121 | if (PG_ARGISNULL(i)) |
122 | count++; |
123 | } |
124 | |
125 | *nargs = PG_NARGS(); |
126 | *nulls = count; |
127 | } |
128 | |
129 | return true; |
130 | } |
131 | |
132 | /* |
133 | * num_nulls() |
134 | * Count the number of NULL arguments |
135 | */ |
136 | Datum |
137 | pg_num_nulls(PG_FUNCTION_ARGS) |
138 | { |
139 | int32 nargs, |
140 | nulls; |
141 | |
142 | if (!count_nulls(fcinfo, &nargs, &nulls)) |
143 | PG_RETURN_NULL(); |
144 | |
145 | PG_RETURN_INT32(nulls); |
146 | } |
147 | |
148 | /* |
149 | * num_nonnulls() |
150 | * Count the number of non-NULL arguments |
151 | */ |
152 | Datum |
153 | pg_num_nonnulls(PG_FUNCTION_ARGS) |
154 | { |
155 | int32 nargs, |
156 | nulls; |
157 | |
158 | if (!count_nulls(fcinfo, &nargs, &nulls)) |
159 | PG_RETURN_NULL(); |
160 | |
161 | PG_RETURN_INT32(nargs - nulls); |
162 | } |
163 | |
164 | |
165 | /* |
166 | * current_database() |
167 | * Expose the current database to the user |
168 | */ |
169 | Datum |
170 | current_database(PG_FUNCTION_ARGS) |
171 | { |
172 | Name db; |
173 | |
174 | db = (Name) palloc(NAMEDATALEN); |
175 | |
176 | namestrcpy(db, get_database_name(MyDatabaseId)); |
177 | PG_RETURN_NAME(db); |
178 | } |
179 | |
180 | |
181 | /* |
182 | * current_query() |
183 | * Expose the current query to the user (useful in stored procedures) |
184 | * We might want to use ActivePortal->sourceText someday. |
185 | */ |
186 | Datum |
187 | current_query(PG_FUNCTION_ARGS) |
188 | { |
189 | /* there is no easy way to access the more concise 'query_string' */ |
190 | if (debug_query_string) |
191 | PG_RETURN_TEXT_P(cstring_to_text(debug_query_string)); |
192 | else |
193 | PG_RETURN_NULL(); |
194 | } |
195 | |
196 | /* Function to find out which databases make use of a tablespace */ |
197 | |
198 | typedef struct |
199 | { |
200 | char *location; |
201 | DIR *dirdesc; |
202 | } ts_db_fctx; |
203 | |
204 | Datum |
205 | pg_tablespace_databases(PG_FUNCTION_ARGS) |
206 | { |
207 | FuncCallContext *funcctx; |
208 | struct dirent *de; |
209 | ts_db_fctx *fctx; |
210 | |
211 | if (SRF_IS_FIRSTCALL()) |
212 | { |
213 | MemoryContext oldcontext; |
214 | Oid tablespaceOid = PG_GETARG_OID(0); |
215 | |
216 | funcctx = SRF_FIRSTCALL_INIT(); |
217 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
218 | |
219 | fctx = palloc(sizeof(ts_db_fctx)); |
220 | |
221 | if (tablespaceOid == GLOBALTABLESPACE_OID) |
222 | { |
223 | fctx->dirdesc = NULL; |
224 | ereport(WARNING, |
225 | (errmsg("global tablespace never has databases" ))); |
226 | } |
227 | else |
228 | { |
229 | if (tablespaceOid == DEFAULTTABLESPACE_OID) |
230 | fctx->location = psprintf("base" ); |
231 | else |
232 | fctx->location = psprintf("pg_tblspc/%u/%s" , tablespaceOid, |
233 | TABLESPACE_VERSION_DIRECTORY); |
234 | |
235 | fctx->dirdesc = AllocateDir(fctx->location); |
236 | |
237 | if (!fctx->dirdesc) |
238 | { |
239 | /* the only expected error is ENOENT */ |
240 | if (errno != ENOENT) |
241 | ereport(ERROR, |
242 | (errcode_for_file_access(), |
243 | errmsg("could not open directory \"%s\": %m" , |
244 | fctx->location))); |
245 | ereport(WARNING, |
246 | (errmsg("%u is not a tablespace OID" , tablespaceOid))); |
247 | } |
248 | } |
249 | funcctx->user_fctx = fctx; |
250 | MemoryContextSwitchTo(oldcontext); |
251 | } |
252 | |
253 | funcctx = SRF_PERCALL_SETUP(); |
254 | fctx = (ts_db_fctx *) funcctx->user_fctx; |
255 | |
256 | if (!fctx->dirdesc) /* not a tablespace */ |
257 | SRF_RETURN_DONE(funcctx); |
258 | |
259 | while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) |
260 | { |
261 | Oid datOid = atooid(de->d_name); |
262 | char *subdir; |
263 | bool isempty; |
264 | |
265 | /* this test skips . and .., but is awfully weak */ |
266 | if (!datOid) |
267 | continue; |
268 | |
269 | /* if database subdir is empty, don't report tablespace as used */ |
270 | |
271 | subdir = psprintf("%s/%s" , fctx->location, de->d_name); |
272 | isempty = directory_is_empty(subdir); |
273 | pfree(subdir); |
274 | |
275 | if (isempty) |
276 | continue; /* indeed, nothing in it */ |
277 | |
278 | SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(datOid)); |
279 | } |
280 | |
281 | FreeDir(fctx->dirdesc); |
282 | SRF_RETURN_DONE(funcctx); |
283 | } |
284 | |
285 | |
286 | /* |
287 | * pg_tablespace_location - get location for a tablespace |
288 | */ |
289 | Datum |
290 | pg_tablespace_location(PG_FUNCTION_ARGS) |
291 | { |
292 | Oid tablespaceOid = PG_GETARG_OID(0); |
293 | char sourcepath[MAXPGPATH]; |
294 | char targetpath[MAXPGPATH]; |
295 | int rllen; |
296 | |
297 | /* |
298 | * It's useful to apply this function to pg_class.reltablespace, wherein |
299 | * zero means "the database's default tablespace". So, rather than |
300 | * throwing an error for zero, we choose to assume that's what is meant. |
301 | */ |
302 | if (tablespaceOid == InvalidOid) |
303 | tablespaceOid = MyDatabaseTableSpace; |
304 | |
305 | /* |
306 | * Return empty string for the cluster's default tablespaces |
307 | */ |
308 | if (tablespaceOid == DEFAULTTABLESPACE_OID || |
309 | tablespaceOid == GLOBALTABLESPACE_OID) |
310 | PG_RETURN_TEXT_P(cstring_to_text("" )); |
311 | |
312 | #if defined(HAVE_READLINK) || defined(WIN32) |
313 | |
314 | /* |
315 | * Find the location of the tablespace by reading the symbolic link that |
316 | * is in pg_tblspc/<oid>. |
317 | */ |
318 | snprintf(sourcepath, sizeof(sourcepath), "pg_tblspc/%u" , tablespaceOid); |
319 | |
320 | rllen = readlink(sourcepath, targetpath, sizeof(targetpath)); |
321 | if (rllen < 0) |
322 | ereport(ERROR, |
323 | (errcode_for_file_access(), |
324 | errmsg("could not read symbolic link \"%s\": %m" , |
325 | sourcepath))); |
326 | if (rllen >= sizeof(targetpath)) |
327 | ereport(ERROR, |
328 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
329 | errmsg("symbolic link \"%s\" target is too long" , |
330 | sourcepath))); |
331 | targetpath[rllen] = '\0'; |
332 | |
333 | PG_RETURN_TEXT_P(cstring_to_text(targetpath)); |
334 | #else |
335 | ereport(ERROR, |
336 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
337 | errmsg("tablespaces are not supported on this platform" ))); |
338 | PG_RETURN_NULL(); |
339 | #endif |
340 | } |
341 | |
342 | /* |
343 | * pg_sleep - delay for N seconds |
344 | */ |
345 | Datum |
346 | pg_sleep(PG_FUNCTION_ARGS) |
347 | { |
348 | float8 secs = PG_GETARG_FLOAT8(0); |
349 | float8 endtime; |
350 | |
351 | /* |
352 | * We sleep using WaitLatch, to ensure that we'll wake up promptly if an |
353 | * important signal (such as SIGALRM or SIGINT) arrives. Because |
354 | * WaitLatch's upper limit of delay is INT_MAX milliseconds, and the user |
355 | * might ask for more than that, we sleep for at most 10 minutes and then |
356 | * loop. |
357 | * |
358 | * By computing the intended stop time initially, we avoid accumulation of |
359 | * extra delay across multiple sleeps. This also ensures we won't delay |
360 | * less than the specified time when WaitLatch is terminated early by a |
361 | * non-query-canceling signal such as SIGHUP. |
362 | */ |
363 | #define GetNowFloat() ((float8) GetCurrentTimestamp() / 1000000.0) |
364 | |
365 | endtime = GetNowFloat() + secs; |
366 | |
367 | for (;;) |
368 | { |
369 | float8 delay; |
370 | long delay_ms; |
371 | |
372 | CHECK_FOR_INTERRUPTS(); |
373 | |
374 | delay = endtime - GetNowFloat(); |
375 | if (delay >= 600.0) |
376 | delay_ms = 600000; |
377 | else if (delay > 0.0) |
378 | delay_ms = (long) ceil(delay * 1000.0); |
379 | else |
380 | break; |
381 | |
382 | (void) WaitLatch(MyLatch, |
383 | WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, |
384 | delay_ms, |
385 | WAIT_EVENT_PG_SLEEP); |
386 | ResetLatch(MyLatch); |
387 | } |
388 | |
389 | PG_RETURN_VOID(); |
390 | } |
391 | |
392 | /* Function to return the list of grammar keywords */ |
393 | Datum |
394 | pg_get_keywords(PG_FUNCTION_ARGS) |
395 | { |
396 | FuncCallContext *funcctx; |
397 | |
398 | if (SRF_IS_FIRSTCALL()) |
399 | { |
400 | MemoryContext oldcontext; |
401 | TupleDesc tupdesc; |
402 | |
403 | funcctx = SRF_FIRSTCALL_INIT(); |
404 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
405 | |
406 | tupdesc = CreateTemplateTupleDesc(3); |
407 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word" , |
408 | TEXTOID, -1, 0); |
409 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode" , |
410 | CHAROID, -1, 0); |
411 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, "catdesc" , |
412 | TEXTOID, -1, 0); |
413 | |
414 | funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); |
415 | |
416 | MemoryContextSwitchTo(oldcontext); |
417 | } |
418 | |
419 | funcctx = SRF_PERCALL_SETUP(); |
420 | |
421 | if (funcctx->call_cntr < ScanKeywords.num_keywords) |
422 | { |
423 | char *values[3]; |
424 | HeapTuple tuple; |
425 | |
426 | /* cast-away-const is ugly but alternatives aren't much better */ |
427 | values[0] = unconstify(char *, |
428 | GetScanKeyword(funcctx->call_cntr, |
429 | &ScanKeywords)); |
430 | |
431 | switch (ScanKeywordCategories[funcctx->call_cntr]) |
432 | { |
433 | case UNRESERVED_KEYWORD: |
434 | values[1] = "U" ; |
435 | values[2] = _("unreserved" ); |
436 | break; |
437 | case COL_NAME_KEYWORD: |
438 | values[1] = "C" ; |
439 | values[2] = _("unreserved (cannot be function or type name)" ); |
440 | break; |
441 | case TYPE_FUNC_NAME_KEYWORD: |
442 | values[1] = "T" ; |
443 | values[2] = _("reserved (can be function or type name)" ); |
444 | break; |
445 | case RESERVED_KEYWORD: |
446 | values[1] = "R" ; |
447 | values[2] = _("reserved" ); |
448 | break; |
449 | default: /* shouldn't be possible */ |
450 | values[1] = NULL; |
451 | values[2] = NULL; |
452 | break; |
453 | } |
454 | |
455 | tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); |
456 | |
457 | SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); |
458 | } |
459 | |
460 | SRF_RETURN_DONE(funcctx); |
461 | } |
462 | |
463 | |
464 | /* |
465 | * Return the type of the argument. |
466 | */ |
467 | Datum |
468 | pg_typeof(PG_FUNCTION_ARGS) |
469 | { |
470 | PG_RETURN_OID(get_fn_expr_argtype(fcinfo->flinfo, 0)); |
471 | } |
472 | |
473 | |
474 | /* |
475 | * Implementation of the COLLATE FOR expression; returns the collation |
476 | * of the argument. |
477 | */ |
478 | Datum |
479 | pg_collation_for(PG_FUNCTION_ARGS) |
480 | { |
481 | Oid typeid; |
482 | Oid collid; |
483 | |
484 | typeid = get_fn_expr_argtype(fcinfo->flinfo, 0); |
485 | if (!typeid) |
486 | PG_RETURN_NULL(); |
487 | if (!type_is_collatable(typeid) && typeid != UNKNOWNOID) |
488 | ereport(ERROR, |
489 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
490 | errmsg("collations are not supported by type %s" , |
491 | format_type_be(typeid)))); |
492 | |
493 | collid = PG_GET_COLLATION(); |
494 | if (!collid) |
495 | PG_RETURN_NULL(); |
496 | PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid))); |
497 | } |
498 | |
499 | |
500 | /* |
501 | * pg_relation_is_updatable - determine which update events the specified |
502 | * relation supports. |
503 | * |
504 | * This relies on relation_is_updatable() in rewriteHandler.c, which see |
505 | * for additional information. |
506 | */ |
507 | Datum |
508 | pg_relation_is_updatable(PG_FUNCTION_ARGS) |
509 | { |
510 | Oid reloid = PG_GETARG_OID(0); |
511 | bool include_triggers = PG_GETARG_BOOL(1); |
512 | |
513 | PG_RETURN_INT32(relation_is_updatable(reloid, include_triggers, NULL)); |
514 | } |
515 | |
516 | /* |
517 | * pg_column_is_updatable - determine whether a column is updatable |
518 | * |
519 | * This function encapsulates the decision about just what |
520 | * information_schema.columns.is_updatable actually means. It's not clear |
521 | * whether deletability of the column's relation should be required, so |
522 | * we want that decision in C code where we could change it without initdb. |
523 | */ |
524 | Datum |
525 | pg_column_is_updatable(PG_FUNCTION_ARGS) |
526 | { |
527 | Oid reloid = PG_GETARG_OID(0); |
528 | AttrNumber attnum = PG_GETARG_INT16(1); |
529 | AttrNumber col = attnum - FirstLowInvalidHeapAttributeNumber; |
530 | bool include_triggers = PG_GETARG_BOOL(2); |
531 | int events; |
532 | |
533 | /* System columns are never updatable */ |
534 | if (attnum <= 0) |
535 | PG_RETURN_BOOL(false); |
536 | |
537 | events = relation_is_updatable(reloid, include_triggers, |
538 | bms_make_singleton(col)); |
539 | |
540 | /* We require both updatability and deletability of the relation */ |
541 | #define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE)) |
542 | |
543 | PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS); |
544 | } |
545 | |
546 | |
547 | /* |
548 | * Is character a valid identifier start? |
549 | * Must match scan.l's {ident_start} character class. |
550 | */ |
551 | static bool |
552 | is_ident_start(unsigned char c) |
553 | { |
554 | /* Underscores and ASCII letters are OK */ |
555 | if (c == '_') |
556 | return true; |
557 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) |
558 | return true; |
559 | /* Any high-bit-set character is OK (might be part of a multibyte char) */ |
560 | if (IS_HIGHBIT_SET(c)) |
561 | return true; |
562 | return false; |
563 | } |
564 | |
565 | /* |
566 | * Is character a valid identifier continuation? |
567 | * Must match scan.l's {ident_cont} character class. |
568 | */ |
569 | static bool |
570 | is_ident_cont(unsigned char c) |
571 | { |
572 | /* Can be digit or dollar sign ... */ |
573 | if ((c >= '0' && c <= '9') || c == '$') |
574 | return true; |
575 | /* ... or an identifier start character */ |
576 | return is_ident_start(c); |
577 | } |
578 | |
579 | /* |
580 | * parse_ident - parse a SQL qualified identifier into separate identifiers. |
581 | * When strict mode is active (second parameter), then any chars after |
582 | * the last identifier are disallowed. |
583 | */ |
584 | Datum |
585 | parse_ident(PG_FUNCTION_ARGS) |
586 | { |
587 | text *qualname = PG_GETARG_TEXT_PP(0); |
588 | bool strict = PG_GETARG_BOOL(1); |
589 | char *qualname_str = text_to_cstring(qualname); |
590 | ArrayBuildState *astate = NULL; |
591 | char *nextp; |
592 | bool after_dot = false; |
593 | |
594 | /* |
595 | * The code below scribbles on qualname_str in some cases, so we should |
596 | * reconvert qualname if we need to show the original string in error |
597 | * messages. |
598 | */ |
599 | nextp = qualname_str; |
600 | |
601 | /* skip leading whitespace */ |
602 | while (scanner_isspace(*nextp)) |
603 | nextp++; |
604 | |
605 | for (;;) |
606 | { |
607 | char *curname; |
608 | bool missing_ident = true; |
609 | |
610 | if (*nextp == '"') |
611 | { |
612 | char *endp; |
613 | |
614 | curname = nextp + 1; |
615 | for (;;) |
616 | { |
617 | endp = strchr(nextp + 1, '"'); |
618 | if (endp == NULL) |
619 | ereport(ERROR, |
620 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
621 | errmsg("string is not a valid identifier: \"%s\"" , |
622 | text_to_cstring(qualname)), |
623 | errdetail("String has unclosed double quotes." ))); |
624 | if (endp[1] != '"') |
625 | break; |
626 | memmove(endp, endp + 1, strlen(endp)); |
627 | nextp = endp; |
628 | } |
629 | nextp = endp + 1; |
630 | *endp = '\0'; |
631 | |
632 | if (endp - curname == 0) |
633 | ereport(ERROR, |
634 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
635 | errmsg("string is not a valid identifier: \"%s\"" , |
636 | text_to_cstring(qualname)), |
637 | errdetail("Quoted identifier must not be empty." ))); |
638 | |
639 | astate = accumArrayResult(astate, CStringGetTextDatum(curname), |
640 | false, TEXTOID, CurrentMemoryContext); |
641 | missing_ident = false; |
642 | } |
643 | else if (is_ident_start((unsigned char) *nextp)) |
644 | { |
645 | char *downname; |
646 | int len; |
647 | text *part; |
648 | |
649 | curname = nextp++; |
650 | while (is_ident_cont((unsigned char) *nextp)) |
651 | nextp++; |
652 | |
653 | len = nextp - curname; |
654 | |
655 | /* |
656 | * We don't implicitly truncate identifiers. This is useful for |
657 | * allowing the user to check for specific parts of the identifier |
658 | * being too long. It's easy enough for the user to get the |
659 | * truncated names by casting our output to name[]. |
660 | */ |
661 | downname = downcase_identifier(curname, len, false, false); |
662 | part = cstring_to_text_with_len(downname, len); |
663 | astate = accumArrayResult(astate, PointerGetDatum(part), false, |
664 | TEXTOID, CurrentMemoryContext); |
665 | missing_ident = false; |
666 | } |
667 | |
668 | if (missing_ident) |
669 | { |
670 | /* Different error messages based on where we failed. */ |
671 | if (*nextp == '.') |
672 | ereport(ERROR, |
673 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
674 | errmsg("string is not a valid identifier: \"%s\"" , |
675 | text_to_cstring(qualname)), |
676 | errdetail("No valid identifier before \".\"." ))); |
677 | else if (after_dot) |
678 | ereport(ERROR, |
679 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
680 | errmsg("string is not a valid identifier: \"%s\"" , |
681 | text_to_cstring(qualname)), |
682 | errdetail("No valid identifier after \".\"." ))); |
683 | else |
684 | ereport(ERROR, |
685 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
686 | errmsg("string is not a valid identifier: \"%s\"" , |
687 | text_to_cstring(qualname)))); |
688 | } |
689 | |
690 | while (scanner_isspace(*nextp)) |
691 | nextp++; |
692 | |
693 | if (*nextp == '.') |
694 | { |
695 | after_dot = true; |
696 | nextp++; |
697 | while (scanner_isspace(*nextp)) |
698 | nextp++; |
699 | } |
700 | else if (*nextp == '\0') |
701 | { |
702 | break; |
703 | } |
704 | else |
705 | { |
706 | if (strict) |
707 | ereport(ERROR, |
708 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
709 | errmsg("string is not a valid identifier: \"%s\"" , |
710 | text_to_cstring(qualname)))); |
711 | break; |
712 | } |
713 | } |
714 | |
715 | PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext)); |
716 | } |
717 | |
718 | /* |
719 | * pg_current_logfile |
720 | * |
721 | * Report current log file used by log collector by scanning current_logfiles. |
722 | */ |
723 | Datum |
724 | pg_current_logfile(PG_FUNCTION_ARGS) |
725 | { |
726 | FILE *fd; |
727 | char lbuffer[MAXPGPATH]; |
728 | char *logfmt; |
729 | char *log_filepath; |
730 | char *log_format = lbuffer; |
731 | char *nlpos; |
732 | |
733 | /* The log format parameter is optional */ |
734 | if (PG_NARGS() == 0 || PG_ARGISNULL(0)) |
735 | logfmt = NULL; |
736 | else |
737 | { |
738 | logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0)); |
739 | |
740 | if (strcmp(logfmt, "stderr" ) != 0 && strcmp(logfmt, "csvlog" ) != 0) |
741 | ereport(ERROR, |
742 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
743 | errmsg("log format \"%s\" is not supported" , logfmt), |
744 | errhint("The supported log formats are \"stderr\" and \"csvlog\"." ))); |
745 | } |
746 | |
747 | fd = AllocateFile(LOG_METAINFO_DATAFILE, "r" ); |
748 | if (fd == NULL) |
749 | { |
750 | if (errno != ENOENT) |
751 | ereport(ERROR, |
752 | (errcode_for_file_access(), |
753 | errmsg("could not read file \"%s\": %m" , |
754 | LOG_METAINFO_DATAFILE))); |
755 | PG_RETURN_NULL(); |
756 | } |
757 | |
758 | /* |
759 | * Read the file to gather current log filename(s) registered by the |
760 | * syslogger. |
761 | */ |
762 | while (fgets(lbuffer, sizeof(lbuffer), fd) != NULL) |
763 | { |
764 | /* |
765 | * Extract log format and log file path from the line; lbuffer == |
766 | * log_format, they share storage. |
767 | */ |
768 | log_filepath = strchr(lbuffer, ' '); |
769 | if (log_filepath == NULL) |
770 | { |
771 | /* Uh oh. No space found, so file content is corrupted. */ |
772 | elog(ERROR, |
773 | "missing space character in \"%s\"" , LOG_METAINFO_DATAFILE); |
774 | break; |
775 | } |
776 | |
777 | *log_filepath = '\0'; |
778 | log_filepath++; |
779 | nlpos = strchr(log_filepath, '\n'); |
780 | if (nlpos == NULL) |
781 | { |
782 | /* Uh oh. No newline found, so file content is corrupted. */ |
783 | elog(ERROR, |
784 | "missing newline character in \"%s\"" , LOG_METAINFO_DATAFILE); |
785 | break; |
786 | } |
787 | *nlpos = '\0'; |
788 | |
789 | if (logfmt == NULL || strcmp(logfmt, log_format) == 0) |
790 | { |
791 | FreeFile(fd); |
792 | PG_RETURN_TEXT_P(cstring_to_text(log_filepath)); |
793 | } |
794 | } |
795 | |
796 | /* Close the current log filename file. */ |
797 | FreeFile(fd); |
798 | |
799 | PG_RETURN_NULL(); |
800 | } |
801 | |
802 | /* |
803 | * Report current log file used by log collector (1 argument version) |
804 | * |
805 | * note: this wrapper is necessary to pass the sanity check in opr_sanity, |
806 | * which checks that all built-in functions that share the implementing C |
807 | * function take the same number of arguments |
808 | */ |
809 | Datum |
810 | pg_current_logfile_1arg(PG_FUNCTION_ARGS) |
811 | { |
812 | return pg_current_logfile(fcinfo); |
813 | } |
814 | |
815 | /* |
816 | * SQL wrapper around RelationGetReplicaIndex(). |
817 | */ |
818 | Datum |
819 | pg_get_replica_identity_index(PG_FUNCTION_ARGS) |
820 | { |
821 | Oid reloid = PG_GETARG_OID(0); |
822 | Oid idxoid; |
823 | Relation rel; |
824 | |
825 | rel = table_open(reloid, AccessShareLock); |
826 | idxoid = RelationGetReplicaIndex(rel); |
827 | table_close(rel, AccessShareLock); |
828 | |
829 | if (OidIsValid(idxoid)) |
830 | PG_RETURN_OID(idxoid); |
831 | else |
832 | PG_RETURN_NULL(); |
833 | } |
834 | |