(Postgres Database Version 18.4©)  

Quelle  misc.c   Sprache: unbekannt

 
/*-------------------------------------------------------------------------
 *
 * misc.c
 *
 *
 * Portions Copyright (c) 1996-2025, 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 <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include <math.h>
#include <unistd.h>

#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/system_fk_info.h"
#include "commands/dbcommands.h"
#include "commands/tablespace.h"
#include "common/keywords.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/miscnodes.h"
#include "parser/parse_type.h"
#include "parser/scansup.h"
#include "pgstat.h"
#include "postmaster/syslogger.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "storage/latch.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/ruleutils.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"


/*
 * structure to cache metadata needed in pg_input_is_valid_common
 */

typedef struct ValidIOData
{
 Oid   typoid;
 int32  typmod;
 bool  typname_constant;
 Oid   typiofunc;
 Oid   typioparam;
 FmgrInfo inputproc;
} ValidIOData;

static bool pg_input_is_valid_common(FunctionCallInfo fcinfo,
          text *txt, text *typname,
          ErrorSaveContext *escontext);


/*
 * 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;
 char    *location;
 DIR     *dirdesc;
 struct dirent *de;

 InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);

 if (tablespaceOid == GLOBALTABLESPACE_OID)
 {
  ereport(WARNING,
    (errmsg("global tablespace never has databases")));
  /* return empty tuplestore */
  return (Datum) 0;
 }

 if (tablespaceOid == DEFAULTTABLESPACE_OID)
  location = "base";
 else
  location = psprintf("%s/%u/%s", PG_TBLSPC_DIR, 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(rsinfo->setResult, rsinfo->setDesc,
        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;
 struct stat st;

 /*
  * 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(""));

 /*
  * Find the location of the tablespace by reading the symbolic link that
  * is in pg_tblspc/<oid>.
 */

 snprintf(sourcepath, sizeof(sourcepath), "%s/%u", PG_TBLSPC_DIR, tablespaceOid);

 /*
  * Before reading the link, check if the source path is a link or a
  * junction point.  Note that a directory is possible for a tablespace
  * created with allow_in_place_tablespaces enabled.  If a directory is
  * found, a relative path to the data directory is returned.
 */

 if (lstat(sourcepath, &st) < 0)
 {
  ereport(ERROR,
    (errcode_for_file_access(),
     errmsg("could not stat file \"%s\": %m",
      sourcepath)));
 }

 if (!S_ISLNK(st.st_mode))
  PG_RETURN_TEXT_P(cstring_to_text(sourcepath));

 /*
  * In presence of a link or a junction point, return the path pointing to.
 */

 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));
}

/*
 * 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);

  if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
   elog(ERROR, "return type must be a row type");
  funcctx->tuple_desc = tupdesc;
  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);
}


/* Function to return the list of catalog foreign key relationships */
Datum
pg_get_catalog_foreign_keys(PG_FUNCTION_ARGS)
{
 FuncCallContext *funcctx;
 FmgrInfo   *arrayinp;

 if (SRF_IS_FIRSTCALL())
 {
  MemoryContext oldcontext;
  TupleDesc tupdesc;

  funcctx = SRF_FIRSTCALL_INIT();
  oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

  if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
   elog(ERROR, "return type must be a row type");
  funcctx->tuple_desc = BlessTupleDesc(tupdesc);

  /*
   * We use array_in to convert the C strings in sys_fk_relationships[]
   * to text arrays.  But we cannot use DirectFunctionCallN to call
   * array_in, and it wouldn't be very efficient if we could.  Fill an
   * FmgrInfo to use for the call.
 */

  arrayinp = (FmgrInfo *) palloc(sizeof(FmgrInfo));
  fmgr_info(F_ARRAY_IN, arrayinp);
  funcctx->user_fctx = arrayinp;

  MemoryContextSwitchTo(oldcontext);
 }

 funcctx = SRF_PERCALL_SETUP();
 arrayinp = (FmgrInfo *) funcctx->user_fctx;

 if (funcctx->call_cntr < lengthof(sys_fk_relationships))
 {
  const SysFKRelationship *fkrel = &sys_fk_relationships[funcctx->call_cntr];
  Datum  values[6];
  bool  nulls[6];
  HeapTuple tuple;

  memset(nulls, falsesizeof(nulls));

  values[0] = ObjectIdGetDatum(fkrel->fk_table);
  values[1] = FunctionCall3(arrayinp,
          CStringGetDatum(fkrel->fk_columns),
          ObjectIdGetDatum(TEXTOID),
          Int32GetDatum(-1));
  values[2] = ObjectIdGetDatum(fkrel->pk_table);
  values[3] = FunctionCall3(arrayinp,
          CStringGetDatum(fkrel->pk_columns),
          ObjectIdGetDatum(TEXTOID),
          Int32GetDatum(-1));
  values[4] = BoolGetDatum(fkrel->is_array);
  values[5] = BoolGetDatum(fkrel->is_opt);

  tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);

  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));
}


/*
 * Return the base type of the argument.
 *  If the given type is a domain, return its base type;
 *  otherwise return the type's own OID.
 *  Return NULL if the type OID doesn't exist or points to a
 *  non-existent base type.
 *
 * This is a SQL-callable version of getBaseType().  Unlike that function,
 * we don't want to fail for a bogus type OID; this is helpful to keep race
 * conditions from turning into query failures when scanning the catalogs.
 * Hence we need our own implementation.
 */

Datum
pg_basetype(PG_FUNCTION_ARGS)
{
 Oid   typid = PG_GETARG_OID(0);

 /*
  * We loop to find the bottom base type in a stack of domains.
 */

 for (;;)
 {
  HeapTuple tup;
  Form_pg_type typTup;

  tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
  if (!HeapTupleIsValid(tup))
   PG_RETURN_NULL(); /* return NULL for bogus OID */
  typTup = (Form_pg_type) GETSTRUCT(tup);
  if (typTup->typtype != TYPTYPE_DOMAIN)
  {
   /* Not a domain, so done */
   ReleaseSysCache(tup);
   break;
  }

  typid = typTup->typbasetype;
  ReleaseSysCache(tup);
 }

 PG_RETURN_OID(typid);
}


/*
 * 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);
}


/*
 * pg_input_is_valid - test whether string is valid input for datatype.
 *
 * Returns true if OK, false if not.
 *
 * This will only work usefully if the datatype's input function has been
 * updated to return "soft" errors via errsave/ereturn.
 */

Datum
pg_input_is_valid(PG_FUNCTION_ARGS)
{
 text    *txt = PG_GETARG_TEXT_PP(0);
 text    *typname = PG_GETARG_TEXT_PP(1);
 ErrorSaveContext escontext = {T_ErrorSaveContext};

 PG_RETURN_BOOL(pg_input_is_valid_common(fcinfo, txt, typname,
           &escontext));
}

/*
 * pg_input_error_info - test whether string is valid input for datatype.
 *
 * Returns NULL if OK, else the primary message, detail message, hint message
 * and sql error code from the error.
 *
 * This will only work usefully if the datatype's input function has been
 * updated to return "soft" errors via errsave/ereturn.
 */

Datum
pg_input_error_info(PG_FUNCTION_ARGS)
{
 text    *txt = PG_GETARG_TEXT_PP(0);
 text    *typname = PG_GETARG_TEXT_PP(1);
 ErrorSaveContext escontext = {T_ErrorSaveContext};
 TupleDesc tupdesc;
 Datum  values[4];
 bool  isnull[4];

 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
  elog(ERROR, "return type must be a row type");

 /* Enable details_wanted */
 escontext.details_wanted = true;

 if (pg_input_is_valid_common(fcinfo, txt, typname,
         &escontext))
  memset(isnull, truesizeof(isnull));
 else
 {
  char    *sqlstate;

  Assert(escontext.error_occurred);
  Assert(escontext.error_data != NULL);
  Assert(escontext.error_data->message != NULL);

  memset(isnull, falsesizeof(isnull));

  values[0] = CStringGetTextDatum(escontext.error_data->message);

  if (escontext.error_data->detail != NULL)
   values[1] = CStringGetTextDatum(escontext.error_data->detail);
  else
   isnull[1] = true;

  if (escontext.error_data->hint != NULL)
   values[2] = CStringGetTextDatum(escontext.error_data->hint);
  else
   isnull[2] = true;

  sqlstate = unpack_sql_state(escontext.error_data->sqlerrcode);
  values[3] = CStringGetTextDatum(sqlstate);
 }

 return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
}

/* Common subroutine for the above */
static bool
pg_input_is_valid_common(FunctionCallInfo fcinfo,
       text *txt, text *typname,
       ErrorSaveContext *escontext)
{
 char    *str = text_to_cstring(txt);
 ValidIOData *my_extra;
 Datum  converted;

 /*
  * We arrange to look up the needed I/O info just once per series of
  * calls, assuming the data type doesn't change underneath us.
 */

 my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra;
 if (my_extra == NULL)
 {
  fcinfo->flinfo->fn_extra =
   MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
          sizeof(ValidIOData));
  my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra;
  my_extra->typoid = InvalidOid;
  /* Detect whether typname argument is constant. */
  my_extra->typname_constant = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
 }

 /*
  * If the typname argument is constant, we only need to parse it the first
  * time through.
 */

 if (my_extra->typoid == InvalidOid || !my_extra->typname_constant)
 {
  char    *typnamestr = text_to_cstring(typname);
  Oid   typoid;

  /* Parse type-name argument to obtain type OID and encoded typmod. */
  (void) parseTypeString(typnamestr, &typoid, &my_extra->typmod, NULL);

  /* Update type-specific info if typoid changed. */
  if (my_extra->typoid != typoid)
  {
   getTypeInputInfo(typoid,
        &my_extra->typiofunc,
        &my_extra->typioparam);
   fmgr_info_cxt(my_extra->typiofunc, &my_extra->inputproc,
        fcinfo->flinfo->fn_mcxt);
   my_extra->typoid = typoid;
  }
 }

 /* Now we can try to perform the conversion. */
 return InputFunctionCallSafe(&my_extra->inputproc,
         str,
         my_extra->typioparam,
         my_extra->typmod,
         (Node *) escontext,
         &converted);
}


/*
 * 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, falsefalse);
   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 &&
   strcmp(logfmt, "jsonlog") != 0)
   ereport(ERROR,
     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      errmsg("log format \"%s\" is not supported", logfmt),
      errhint("The supported log formats are \"stderr\", \"csvlog\", and \"jsonlog\".")));
 }

 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();
}

/*
 * Transition function for the ANY_VALUE aggregate
 */

Datum
any_value_transfn(PG_FUNCTION_ARGS)
{
 PG_RETURN_DATUM(PG_GETARG_DATUM(0));
}

Messung V0.5 in Prozent
C=93 H=93 G=92

[zur Elbe Produktseite wechseln0.20QuellennavigatorsAnalyse erneut starten2026-08-08]