mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
Up to now, if you tried to omit "AS" before a column label in a SELECT list, it would only work if the column label was an IDENT, that is not any known keyword. This is rather unfriendly considering that we have so many keywords and are constantly growing more. In the wake of commit 1ed6b8956 it's possible to improve matters quite a bit. We'd originally tried to make this work by having some of the existing keyword categories be allowed without AS, but that didn't work too well, because each category contains a few special cases that don't work without AS. Instead, invent an entirely orthogonal keyword property "can be bare column label", and mark all keywords that way for which we don't get shift/reduce errors by doing so. It turns out that of our 450 current keywords, all but 39 can be made bare column labels, improving the situation by over 90%. This number might move around a little depending on future grammar work, but it's a pretty nice improvement. Mark Dilger, based on work by myself and Robert Haas; review by John Naylor Discussion: https://postgr.es/m/38ca86db-42ab-9b48-2902-337a0d6b8311@2ndquadrant.com
866 lines
21 KiB
C
866 lines
21 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* misc.c
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/adt/misc.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <sys/file.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/sysattr.h"
|
|
#include "access/table.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/dbcommands.h"
|
|
#include "commands/tablespace.h"
|
|
#include "common/keywords.h"
|
|
#include "funcapi.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/scansup.h"
|
|
#include "pgstat.h"
|
|
#include "postmaster/syslogger.h"
|
|
#include "rewrite/rewriteHandler.h"
|
|
#include "storage/fd.h"
|
|
#include "tcop/tcopprot.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/ruleutils.h"
|
|
#include "utils/timestamp.h"
|
|
|
|
/*
|
|
* Common subroutine for num_nulls() and num_nonnulls().
|
|
* Returns true if successful, false if function should return NULL.
|
|
* If successful, total argument count and number of nulls are
|
|
* returned into *nargs and *nulls.
|
|
*/
|
|
static bool
|
|
count_nulls(FunctionCallInfo fcinfo,
|
|
int32 *nargs, int32 *nulls)
|
|
{
|
|
int32 count = 0;
|
|
int i;
|
|
|
|
/* Did we get a VARIADIC array argument, or separate arguments? */
|
|
if (get_fn_expr_variadic(fcinfo->flinfo))
|
|
{
|
|
ArrayType *arr;
|
|
int ndims,
|
|
nitems,
|
|
*dims;
|
|
bits8 *bitmap;
|
|
|
|
Assert(PG_NARGS() == 1);
|
|
|
|
/*
|
|
* If we get a null as VARIADIC array argument, we can't say anything
|
|
* useful about the number of elements, so return NULL. This behavior
|
|
* is consistent with other variadic functions - see concat_internal.
|
|
*/
|
|
if (PG_ARGISNULL(0))
|
|
return false;
|
|
|
|
/*
|
|
* Non-null argument had better be an array. We assume that any call
|
|
* context that could let get_fn_expr_variadic return true will have
|
|
* checked that a VARIADIC-labeled parameter actually is an array. So
|
|
* it should be okay to just Assert that it's an array rather than
|
|
* doing a full-fledged error check.
|
|
*/
|
|
Assert(OidIsValid(get_base_element_type(get_fn_expr_argtype(fcinfo->flinfo, 0))));
|
|
|
|
/* OK, safe to fetch the array value */
|
|
arr = PG_GETARG_ARRAYTYPE_P(0);
|
|
|
|
/* Count the array elements */
|
|
ndims = ARR_NDIM(arr);
|
|
dims = ARR_DIMS(arr);
|
|
nitems = ArrayGetNItems(ndims, dims);
|
|
|
|
/* Count those that are NULL */
|
|
bitmap = ARR_NULLBITMAP(arr);
|
|
if (bitmap)
|
|
{
|
|
int bitmask = 1;
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
if ((*bitmap & bitmask) == 0)
|
|
count++;
|
|
|
|
bitmask <<= 1;
|
|
if (bitmask == 0x100)
|
|
{
|
|
bitmap++;
|
|
bitmask = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
*nargs = nitems;
|
|
*nulls = count;
|
|
}
|
|
else
|
|
{
|
|
/* Separate arguments, so just count 'em */
|
|
for (i = 0; i < PG_NARGS(); i++)
|
|
{
|
|
if (PG_ARGISNULL(i))
|
|
count++;
|
|
}
|
|
|
|
*nargs = PG_NARGS();
|
|
*nulls = count;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* num_nulls()
|
|
* Count the number of NULL arguments
|
|
*/
|
|
Datum
|
|
pg_num_nulls(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 nargs,
|
|
nulls;
|
|
|
|
if (!count_nulls(fcinfo, &nargs, &nulls))
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_INT32(nulls);
|
|
}
|
|
|
|
/*
|
|
* num_nonnulls()
|
|
* Count the number of non-NULL arguments
|
|
*/
|
|
Datum
|
|
pg_num_nonnulls(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 nargs,
|
|
nulls;
|
|
|
|
if (!count_nulls(fcinfo, &nargs, &nulls))
|
|
PG_RETURN_NULL();
|
|
|
|
PG_RETURN_INT32(nargs - nulls);
|
|
}
|
|
|
|
|
|
/*
|
|
* current_database()
|
|
* Expose the current database to the user
|
|
*/
|
|
Datum
|
|
current_database(PG_FUNCTION_ARGS)
|
|
{
|
|
Name db;
|
|
|
|
db = (Name) palloc(NAMEDATALEN);
|
|
|
|
namestrcpy(db, get_database_name(MyDatabaseId));
|
|
PG_RETURN_NAME(db);
|
|
}
|
|
|
|
|
|
/*
|
|
* current_query()
|
|
* Expose the current query to the user (useful in stored procedures)
|
|
* We might want to use ActivePortal->sourceText someday.
|
|
*/
|
|
Datum
|
|
current_query(PG_FUNCTION_ARGS)
|
|
{
|
|
/* there is no easy way to access the more concise 'query_string' */
|
|
if (debug_query_string)
|
|
PG_RETURN_TEXT_P(cstring_to_text(debug_query_string));
|
|
else
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/* Function to find out which databases make use of a tablespace */
|
|
|
|
Datum
|
|
pg_tablespace_databases(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid tablespaceOid = PG_GETARG_OID(0);
|
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
|
bool randomAccess;
|
|
TupleDesc tupdesc;
|
|
Tuplestorestate *tupstore;
|
|
char *location;
|
|
DIR *dirdesc;
|
|
struct dirent *de;
|
|
MemoryContext oldcontext;
|
|
|
|
/* check to see if caller supports us returning a tuplestore */
|
|
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("set-valued function called in context that cannot accept a set")));
|
|
if (!(rsinfo->allowedModes & SFRM_Materialize))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("materialize mode required, but it is not allowed in this context")));
|
|
|
|
/* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
|
|
oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
|
|
|
|
tupdesc = CreateTemplateTupleDesc(1);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases",
|
|
OIDOID, -1, 0);
|
|
|
|
randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
|
|
tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
|
|
|
|
rsinfo->returnMode = SFRM_Materialize;
|
|
rsinfo->setResult = tupstore;
|
|
rsinfo->setDesc = tupdesc;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
if (tablespaceOid == GLOBALTABLESPACE_OID)
|
|
{
|
|
ereport(WARNING,
|
|
(errmsg("global tablespace never has databases")));
|
|
/* return empty tuplestore */
|
|
return (Datum) 0;
|
|
}
|
|
|
|
if (tablespaceOid == DEFAULTTABLESPACE_OID)
|
|
location = psprintf("base");
|
|
else
|
|
location = psprintf("pg_tblspc/%u/%s", tablespaceOid,
|
|
TABLESPACE_VERSION_DIRECTORY);
|
|
|
|
dirdesc = AllocateDir(location);
|
|
|
|
if (!dirdesc)
|
|
{
|
|
/* the only expected error is ENOENT */
|
|
if (errno != ENOENT)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open directory \"%s\": %m",
|
|
location)));
|
|
ereport(WARNING,
|
|
(errmsg("%u is not a tablespace OID", tablespaceOid)));
|
|
/* return empty tuplestore */
|
|
return (Datum) 0;
|
|
}
|
|
|
|
while ((de = ReadDir(dirdesc, location)) != NULL)
|
|
{
|
|
Oid datOid = atooid(de->d_name);
|
|
char *subdir;
|
|
bool isempty;
|
|
Datum values[1];
|
|
bool nulls[1];
|
|
|
|
/* this test skips . and .., but is awfully weak */
|
|
if (!datOid)
|
|
continue;
|
|
|
|
/* if database subdir is empty, don't report tablespace as used */
|
|
|
|
subdir = psprintf("%s/%s", location, de->d_name);
|
|
isempty = directory_is_empty(subdir);
|
|
pfree(subdir);
|
|
|
|
if (isempty)
|
|
continue; /* indeed, nothing in it */
|
|
|
|
values[0] = ObjectIdGetDatum(datOid);
|
|
nulls[0] = false;
|
|
|
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
|
}
|
|
|
|
FreeDir(dirdesc);
|
|
return (Datum) 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* pg_tablespace_location - get location for a tablespace
|
|
*/
|
|
Datum
|
|
pg_tablespace_location(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid tablespaceOid = PG_GETARG_OID(0);
|
|
char sourcepath[MAXPGPATH];
|
|
char targetpath[MAXPGPATH];
|
|
int rllen;
|
|
|
|
/*
|
|
* It's useful to apply this function to pg_class.reltablespace, wherein
|
|
* zero means "the database's default tablespace". So, rather than
|
|
* throwing an error for zero, we choose to assume that's what is meant.
|
|
*/
|
|
if (tablespaceOid == InvalidOid)
|
|
tablespaceOid = MyDatabaseTableSpace;
|
|
|
|
/*
|
|
* Return empty string for the cluster's default tablespaces
|
|
*/
|
|
if (tablespaceOid == DEFAULTTABLESPACE_OID ||
|
|
tablespaceOid == GLOBALTABLESPACE_OID)
|
|
PG_RETURN_TEXT_P(cstring_to_text(""));
|
|
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
|
|
/*
|
|
* Find the location of the tablespace by reading the symbolic link that
|
|
* is in pg_tblspc/<oid>.
|
|
*/
|
|
snprintf(sourcepath, sizeof(sourcepath), "pg_tblspc/%u", tablespaceOid);
|
|
|
|
rllen = readlink(sourcepath, targetpath, sizeof(targetpath));
|
|
if (rllen < 0)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not read symbolic link \"%s\": %m",
|
|
sourcepath)));
|
|
if (rllen >= sizeof(targetpath))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
errmsg("symbolic link \"%s\" target is too long",
|
|
sourcepath)));
|
|
targetpath[rllen] = '\0';
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(targetpath));
|
|
#else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("tablespaces are not supported on this platform")));
|
|
PG_RETURN_NULL();
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* pg_sleep - delay for N seconds
|
|
*/
|
|
Datum
|
|
pg_sleep(PG_FUNCTION_ARGS)
|
|
{
|
|
float8 secs = PG_GETARG_FLOAT8(0);
|
|
float8 endtime;
|
|
|
|
/*
|
|
* We sleep using WaitLatch, to ensure that we'll wake up promptly if an
|
|
* important signal (such as SIGALRM or SIGINT) arrives. Because
|
|
* WaitLatch's upper limit of delay is INT_MAX milliseconds, and the user
|
|
* might ask for more than that, we sleep for at most 10 minutes and then
|
|
* loop.
|
|
*
|
|
* By computing the intended stop time initially, we avoid accumulation of
|
|
* extra delay across multiple sleeps. This also ensures we won't delay
|
|
* less than the specified time when WaitLatch is terminated early by a
|
|
* non-query-canceling signal such as SIGHUP.
|
|
*/
|
|
#define GetNowFloat() ((float8) GetCurrentTimestamp() / 1000000.0)
|
|
|
|
endtime = GetNowFloat() + secs;
|
|
|
|
for (;;)
|
|
{
|
|
float8 delay;
|
|
long delay_ms;
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
delay = endtime - GetNowFloat();
|
|
if (delay >= 600.0)
|
|
delay_ms = 600000;
|
|
else if (delay > 0.0)
|
|
delay_ms = (long) ceil(delay * 1000.0);
|
|
else
|
|
break;
|
|
|
|
(void) WaitLatch(MyLatch,
|
|
WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
|
|
delay_ms,
|
|
WAIT_EVENT_PG_SLEEP);
|
|
ResetLatch(MyLatch);
|
|
}
|
|
|
|
PG_RETURN_VOID();
|
|
}
|
|
|
|
/* Function to return the list of grammar keywords */
|
|
Datum
|
|
pg_get_keywords(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
MemoryContext oldcontext;
|
|
TupleDesc tupdesc;
|
|
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
tupdesc = CreateTemplateTupleDesc(5);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode",
|
|
CHAROID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "barelabel",
|
|
BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "catdesc",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "baredesc",
|
|
TEXTOID, -1, 0);
|
|
|
|
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
|
|
if (funcctx->call_cntr < ScanKeywords.num_keywords)
|
|
{
|
|
char *values[5];
|
|
HeapTuple tuple;
|
|
|
|
/* cast-away-const is ugly but alternatives aren't much better */
|
|
values[0] = unconstify(char *,
|
|
GetScanKeyword(funcctx->call_cntr,
|
|
&ScanKeywords));
|
|
|
|
switch (ScanKeywordCategories[funcctx->call_cntr])
|
|
{
|
|
case UNRESERVED_KEYWORD:
|
|
values[1] = "U";
|
|
values[3] = _("unreserved");
|
|
break;
|
|
case COL_NAME_KEYWORD:
|
|
values[1] = "C";
|
|
values[3] = _("unreserved (cannot be function or type name)");
|
|
break;
|
|
case TYPE_FUNC_NAME_KEYWORD:
|
|
values[1] = "T";
|
|
values[3] = _("reserved (can be function or type name)");
|
|
break;
|
|
case RESERVED_KEYWORD:
|
|
values[1] = "R";
|
|
values[3] = _("reserved");
|
|
break;
|
|
default: /* shouldn't be possible */
|
|
values[1] = NULL;
|
|
values[3] = NULL;
|
|
break;
|
|
}
|
|
|
|
if (ScanKeywordBareLabel[funcctx->call_cntr])
|
|
{
|
|
values[2] = "true";
|
|
values[4] = _("can be bare label");
|
|
}
|
|
else
|
|
{
|
|
values[2] = "false";
|
|
values[4] = _("requires AS");
|
|
}
|
|
|
|
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
|
|
|
|
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
|
|
}
|
|
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the type of the argument.
|
|
*/
|
|
Datum
|
|
pg_typeof(PG_FUNCTION_ARGS)
|
|
{
|
|
PG_RETURN_OID(get_fn_expr_argtype(fcinfo->flinfo, 0));
|
|
}
|
|
|
|
|
|
/*
|
|
* Implementation of the COLLATE FOR expression; returns the collation
|
|
* of the argument.
|
|
*/
|
|
Datum
|
|
pg_collation_for(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid typeid;
|
|
Oid collid;
|
|
|
|
typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
|
|
if (!typeid)
|
|
PG_RETURN_NULL();
|
|
if (!type_is_collatable(typeid) && typeid != UNKNOWNOID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
|
errmsg("collations are not supported by type %s",
|
|
format_type_be(typeid))));
|
|
|
|
collid = PG_GET_COLLATION();
|
|
if (!collid)
|
|
PG_RETURN_NULL();
|
|
PG_RETURN_TEXT_P(cstring_to_text(generate_collation_name(collid)));
|
|
}
|
|
|
|
|
|
/*
|
|
* pg_relation_is_updatable - determine which update events the specified
|
|
* relation supports.
|
|
*
|
|
* This relies on relation_is_updatable() in rewriteHandler.c, which see
|
|
* for additional information.
|
|
*/
|
|
Datum
|
|
pg_relation_is_updatable(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid reloid = PG_GETARG_OID(0);
|
|
bool include_triggers = PG_GETARG_BOOL(1);
|
|
|
|
PG_RETURN_INT32(relation_is_updatable(reloid, NIL, include_triggers, NULL));
|
|
}
|
|
|
|
/*
|
|
* pg_column_is_updatable - determine whether a column is updatable
|
|
*
|
|
* This function encapsulates the decision about just what
|
|
* information_schema.columns.is_updatable actually means. It's not clear
|
|
* whether deletability of the column's relation should be required, so
|
|
* we want that decision in C code where we could change it without initdb.
|
|
*/
|
|
Datum
|
|
pg_column_is_updatable(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid reloid = PG_GETARG_OID(0);
|
|
AttrNumber attnum = PG_GETARG_INT16(1);
|
|
AttrNumber col = attnum - FirstLowInvalidHeapAttributeNumber;
|
|
bool include_triggers = PG_GETARG_BOOL(2);
|
|
int events;
|
|
|
|
/* System columns are never updatable */
|
|
if (attnum <= 0)
|
|
PG_RETURN_BOOL(false);
|
|
|
|
events = relation_is_updatable(reloid, NIL, include_triggers,
|
|
bms_make_singleton(col));
|
|
|
|
/* We require both updatability and deletability of the relation */
|
|
#define REQ_EVENTS ((1 << CMD_UPDATE) | (1 << CMD_DELETE))
|
|
|
|
PG_RETURN_BOOL((events & REQ_EVENTS) == REQ_EVENTS);
|
|
}
|
|
|
|
|
|
/*
|
|
* Is character a valid identifier start?
|
|
* Must match scan.l's {ident_start} character class.
|
|
*/
|
|
static bool
|
|
is_ident_start(unsigned char c)
|
|
{
|
|
/* Underscores and ASCII letters are OK */
|
|
if (c == '_')
|
|
return true;
|
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
|
|
return true;
|
|
/* Any high-bit-set character is OK (might be part of a multibyte char) */
|
|
if (IS_HIGHBIT_SET(c))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Is character a valid identifier continuation?
|
|
* Must match scan.l's {ident_cont} character class.
|
|
*/
|
|
static bool
|
|
is_ident_cont(unsigned char c)
|
|
{
|
|
/* Can be digit or dollar sign ... */
|
|
if ((c >= '0' && c <= '9') || c == '$')
|
|
return true;
|
|
/* ... or an identifier start character */
|
|
return is_ident_start(c);
|
|
}
|
|
|
|
/*
|
|
* parse_ident - parse a SQL qualified identifier into separate identifiers.
|
|
* When strict mode is active (second parameter), then any chars after
|
|
* the last identifier are disallowed.
|
|
*/
|
|
Datum
|
|
parse_ident(PG_FUNCTION_ARGS)
|
|
{
|
|
text *qualname = PG_GETARG_TEXT_PP(0);
|
|
bool strict = PG_GETARG_BOOL(1);
|
|
char *qualname_str = text_to_cstring(qualname);
|
|
ArrayBuildState *astate = NULL;
|
|
char *nextp;
|
|
bool after_dot = false;
|
|
|
|
/*
|
|
* The code below scribbles on qualname_str in some cases, so we should
|
|
* reconvert qualname if we need to show the original string in error
|
|
* messages.
|
|
*/
|
|
nextp = qualname_str;
|
|
|
|
/* skip leading whitespace */
|
|
while (scanner_isspace(*nextp))
|
|
nextp++;
|
|
|
|
for (;;)
|
|
{
|
|
char *curname;
|
|
bool missing_ident = true;
|
|
|
|
if (*nextp == '"')
|
|
{
|
|
char *endp;
|
|
|
|
curname = nextp + 1;
|
|
for (;;)
|
|
{
|
|
endp = strchr(nextp + 1, '"');
|
|
if (endp == NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname)),
|
|
errdetail("String has unclosed double quotes.")));
|
|
if (endp[1] != '"')
|
|
break;
|
|
memmove(endp, endp + 1, strlen(endp));
|
|
nextp = endp;
|
|
}
|
|
nextp = endp + 1;
|
|
*endp = '\0';
|
|
|
|
if (endp - curname == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname)),
|
|
errdetail("Quoted identifier must not be empty.")));
|
|
|
|
astate = accumArrayResult(astate, CStringGetTextDatum(curname),
|
|
false, TEXTOID, CurrentMemoryContext);
|
|
missing_ident = false;
|
|
}
|
|
else if (is_ident_start((unsigned char) *nextp))
|
|
{
|
|
char *downname;
|
|
int len;
|
|
text *part;
|
|
|
|
curname = nextp++;
|
|
while (is_ident_cont((unsigned char) *nextp))
|
|
nextp++;
|
|
|
|
len = nextp - curname;
|
|
|
|
/*
|
|
* We don't implicitly truncate identifiers. This is useful for
|
|
* allowing the user to check for specific parts of the identifier
|
|
* being too long. It's easy enough for the user to get the
|
|
* truncated names by casting our output to name[].
|
|
*/
|
|
downname = downcase_identifier(curname, len, false, false);
|
|
part = cstring_to_text_with_len(downname, len);
|
|
astate = accumArrayResult(astate, PointerGetDatum(part), false,
|
|
TEXTOID, CurrentMemoryContext);
|
|
missing_ident = false;
|
|
}
|
|
|
|
if (missing_ident)
|
|
{
|
|
/* Different error messages based on where we failed. */
|
|
if (*nextp == '.')
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname)),
|
|
errdetail("No valid identifier before \".\".")));
|
|
else if (after_dot)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname)),
|
|
errdetail("No valid identifier after \".\".")));
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname))));
|
|
}
|
|
|
|
while (scanner_isspace(*nextp))
|
|
nextp++;
|
|
|
|
if (*nextp == '.')
|
|
{
|
|
after_dot = true;
|
|
nextp++;
|
|
while (scanner_isspace(*nextp))
|
|
nextp++;
|
|
}
|
|
else if (*nextp == '\0')
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (strict)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("string is not a valid identifier: \"%s\"",
|
|
text_to_cstring(qualname))));
|
|
break;
|
|
}
|
|
}
|
|
|
|
PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
|
|
}
|
|
|
|
/*
|
|
* pg_current_logfile
|
|
*
|
|
* Report current log file used by log collector by scanning current_logfiles.
|
|
*/
|
|
Datum
|
|
pg_current_logfile(PG_FUNCTION_ARGS)
|
|
{
|
|
FILE *fd;
|
|
char lbuffer[MAXPGPATH];
|
|
char *logfmt;
|
|
|
|
/* The log format parameter is optional */
|
|
if (PG_NARGS() == 0 || PG_ARGISNULL(0))
|
|
logfmt = NULL;
|
|
else
|
|
{
|
|
logfmt = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
|
|
|
if (strcmp(logfmt, "stderr") != 0 && strcmp(logfmt, "csvlog") != 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("log format \"%s\" is not supported", logfmt),
|
|
errhint("The supported log formats are \"stderr\" and \"csvlog\".")));
|
|
}
|
|
|
|
fd = AllocateFile(LOG_METAINFO_DATAFILE, "r");
|
|
if (fd == NULL)
|
|
{
|
|
if (errno != ENOENT)
|
|
ereport(ERROR,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not read file \"%s\": %m",
|
|
LOG_METAINFO_DATAFILE)));
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* syslogger.c writes CRLF line endings on Windows */
|
|
_setmode(_fileno(fd), _O_TEXT);
|
|
#endif
|
|
|
|
/*
|
|
* Read the file to gather current log filename(s) registered by the
|
|
* syslogger.
|
|
*/
|
|
while (fgets(lbuffer, sizeof(lbuffer), fd) != NULL)
|
|
{
|
|
char *log_format;
|
|
char *log_filepath;
|
|
char *nlpos;
|
|
|
|
/* Extract log format and log file path from the line. */
|
|
log_format = lbuffer;
|
|
log_filepath = strchr(lbuffer, ' ');
|
|
if (log_filepath == NULL)
|
|
{
|
|
/* Uh oh. No space found, so file content is corrupted. */
|
|
elog(ERROR,
|
|
"missing space character in \"%s\"", LOG_METAINFO_DATAFILE);
|
|
break;
|
|
}
|
|
|
|
*log_filepath = '\0';
|
|
log_filepath++;
|
|
nlpos = strchr(log_filepath, '\n');
|
|
if (nlpos == NULL)
|
|
{
|
|
/* Uh oh. No newline found, so file content is corrupted. */
|
|
elog(ERROR,
|
|
"missing newline character in \"%s\"", LOG_METAINFO_DATAFILE);
|
|
break;
|
|
}
|
|
*nlpos = '\0';
|
|
|
|
if (logfmt == NULL || strcmp(logfmt, log_format) == 0)
|
|
{
|
|
FreeFile(fd);
|
|
PG_RETURN_TEXT_P(cstring_to_text(log_filepath));
|
|
}
|
|
}
|
|
|
|
/* Close the current log filename file. */
|
|
FreeFile(fd);
|
|
|
|
PG_RETURN_NULL();
|
|
}
|
|
|
|
/*
|
|
* Report current log file used by log collector (1 argument version)
|
|
*
|
|
* note: this wrapper is necessary to pass the sanity check in opr_sanity,
|
|
* which checks that all built-in functions that share the implementing C
|
|
* function take the same number of arguments
|
|
*/
|
|
Datum
|
|
pg_current_logfile_1arg(PG_FUNCTION_ARGS)
|
|
{
|
|
return pg_current_logfile(fcinfo);
|
|
}
|
|
|
|
/*
|
|
* SQL wrapper around RelationGetReplicaIndex().
|
|
*/
|
|
Datum
|
|
pg_get_replica_identity_index(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid reloid = PG_GETARG_OID(0);
|
|
Oid idxoid;
|
|
Relation rel;
|
|
|
|
rel = table_open(reloid, AccessShareLock);
|
|
idxoid = RelationGetReplicaIndex(rel);
|
|
table_close(rel, AccessShareLock);
|
|
|
|
if (OidIsValid(idxoid))
|
|
PG_RETURN_OID(idxoid);
|
|
else
|
|
PG_RETURN_NULL();
|
|
}
|