1996-07-09 06:22:35 +00:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-13 23:22:53 +00:00
|
|
|
* name.c
|
1997-09-07 05:04:48 +00:00
|
|
|
* Functions for the built-in type "name".
|
2003-05-09 21:19:50 +00:00
|
|
|
*
|
1996-07-09 06:22:35 +00:00
|
|
|
* name replaces char16 and is carefully implemented so that it
|
2003-05-09 21:19:50 +00:00
|
|
|
* is a string of physical length NAMEDATALEN.
|
|
|
|
* DO NOT use hard-coded constants anywhere
|
1996-07-09 06:22:35 +00:00
|
|
|
* always use NAMEDATALEN as the symbolic constant! - jolly 8/21/95
|
1997-09-07 05:04:48 +00:00
|
|
|
*
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
2015-01-06 11:43:47 -05:00
|
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
2000-01-26 05:58:53 +00:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/utils/adt/name.c
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
2000-08-03 16:35:08 +00:00
|
|
|
|
2002-04-26 01:24:08 +00:00
|
|
|
#include "catalog/namespace.h"
|
2002-08-26 17:54:02 +00:00
|
|
|
#include "catalog/pg_type.h"
|
2003-05-09 21:19:50 +00:00
|
|
|
#include "libpq/pqformat.h"
|
|
|
|
#include "mb/pg_wchar.h"
|
2000-08-03 16:35:08 +00:00
|
|
|
#include "miscadmin.h"
|
2002-04-26 01:24:08 +00:00
|
|
|
#include "utils/array.h"
|
1999-07-16 03:14:30 +00:00
|
|
|
#include "utils/builtins.h"
|
2002-04-26 01:24:08 +00:00
|
|
|
#include "utils/lsyscache.h"
|
2003-05-09 21:19:50 +00:00
|
|
|
|
1996-07-09 06:22:35 +00:00
|
|
|
|
1997-09-07 05:04:48 +00:00
|
|
|
/*****************************************************************************
|
|
|
|
* USER I/O ROUTINES (none) *
|
1996-07-09 06:22:35 +00:00
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
1997-09-07 05:04:48 +00:00
|
|
|
* namein - converts "..." to internal representation
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
1997-09-07 05:04:48 +00:00
|
|
|
* Note:
|
|
|
|
* [Old] Currently if strlen(s) < NAMEDATALEN, the extra chars are nulls
|
|
|
|
* Now, always NULL terminated
|
1996-07-09 06:22:35 +00:00
|
|
|
*/
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namein(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
char *s = PG_GETARG_CSTRING(0);
|
2012-05-25 17:34:51 -04:00
|
|
|
Name result;
|
1998-05-29 13:31:52 +00:00
|
|
|
int len;
|
2002-06-13 06:19:45 +00:00
|
|
|
|
|
|
|
len = strlen(s);
|
1997-09-07 05:04:48 +00:00
|
|
|
|
2012-05-25 17:34:51 -04:00
|
|
|
/* Truncate oversize input */
|
|
|
|
if (len >= NAMEDATALEN)
|
|
|
|
len = pg_mbcliplen(s, len, NAMEDATALEN - 1);
|
|
|
|
|
|
|
|
/* We use palloc0 here to ensure result is zero-padded */
|
|
|
|
result = (Name) palloc0(NAMEDATALEN);
|
2002-06-13 06:19:45 +00:00
|
|
|
memcpy(NameStr(*result), s, len);
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
PG_RETURN_NAME(result);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2000-08-03 16:35:08 +00:00
|
|
|
* nameout - converts internal representation to "..."
|
1996-07-09 06:22:35 +00:00
|
|
|
*/
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
nameout(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name s = PG_GETARG_NAME(0);
|
|
|
|
|
|
|
|
PG_RETURN_CSTRING(pstrdup(NameStr(*s)));
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2003-05-09 21:19:50 +00:00
|
|
|
/*
|
|
|
|
* namerecv - converts external binary format to name
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
namerecv(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
|
|
|
|
Name result;
|
|
|
|
char *str;
|
|
|
|
int nbytes;
|
|
|
|
|
|
|
|
str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
|
|
|
|
if (nbytes >= NAMEDATALEN)
|
2003-07-27 04:53:12 +00:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_NAME_TOO_LONG),
|
|
|
|
errmsg("identifier too long"),
|
|
|
|
errdetail("Identifier must be less than %d characters.",
|
2003-08-04 00:43:34 +00:00
|
|
|
NAMEDATALEN)));
|
2003-05-09 21:19:50 +00:00
|
|
|
result = (NameData *) palloc0(NAMEDATALEN);
|
|
|
|
memcpy(result, str, nbytes);
|
|
|
|
pfree(str);
|
|
|
|
PG_RETURN_NAME(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* namesend - converts name to binary format
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
namesend(PG_FUNCTION_ARGS)
|
|
|
|
{
|
|
|
|
Name s = PG_GETARG_NAME(0);
|
|
|
|
StringInfoData buf;
|
|
|
|
|
|
|
|
pq_begintypsend(&buf);
|
|
|
|
pq_sendtext(&buf, NameStr(*s), strlen(NameStr(*s)));
|
|
|
|
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
|
|
|
|
}
|
|
|
|
|
1996-07-09 06:22:35 +00:00
|
|
|
|
1997-09-07 05:04:48 +00:00
|
|
|
/*****************************************************************************
|
|
|
|
* PUBLIC ROUTINES *
|
1996-07-09 06:22:35 +00:00
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/*
|
1997-09-07 05:04:48 +00:00
|
|
|
* nameeq - returns 1 iff arguments are equal
|
|
|
|
* namene - returns 1 iff arguments are not equal
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
1997-09-07 05:04:48 +00:00
|
|
|
* BUGS:
|
|
|
|
* Assumes that "xy\0\0a" should be equal to "xy\0b".
|
|
|
|
* If not, can do the comparison backwards for efficiency.
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
1997-09-07 05:04:48 +00:00
|
|
|
* namelt - returns 1 iff a < b
|
|
|
|
* namele - returns 1 iff a <= b
|
2006-05-30 05:22:59 +00:00
|
|
|
* namegt - returns 1 iff a > b
|
|
|
|
* namege - returns 1 iff a >= b
|
1996-07-09 06:22:35 +00:00
|
|
|
*
|
|
|
|
*/
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
nameeq(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) == 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namene(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) != 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namelt(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) < 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namele(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
|
|
|
|
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) <= 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namegt(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
1997-09-07 05:04:48 +00:00
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) > 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
Datum
|
|
|
|
namege(PG_FUNCTION_ARGS)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2000-08-03 16:35:08 +00:00
|
|
|
Name arg1 = PG_GETARG_NAME(0);
|
|
|
|
Name arg2 = PG_GETARG_NAME(1);
|
1997-09-07 05:04:48 +00:00
|
|
|
|
2000-08-03 16:35:08 +00:00
|
|
|
PG_RETURN_BOOL(strncmp(NameStr(*arg1), NameStr(*arg2), NAMEDATALEN) >= 0);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* (see char.c for comparison/operation routines) */
|
|
|
|
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
|
|
|
namecpy(Name n1, Name n2)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
1997-09-07 05:04:48 +00:00
|
|
|
if (!n1 || !n2)
|
1998-09-01 03:29:17 +00:00
|
|
|
return -1;
|
Replace a bunch more uses of strncpy() with safer coding.
strncpy() has a well-deserved reputation for being unsafe, so make an
effort to get rid of nearly all occurrences in HEAD.
A large fraction of the remaining uses were passing length less than or
equal to the known strlen() of the source, in which case no null-padding
can occur and the behavior is equivalent to memcpy(), though doubtless
slower and certainly harder to reason about. So just use memcpy() in
these cases.
In other cases, use either StrNCpy() or strlcpy() as appropriate (depending
on whether padding to the full length of the destination buffer seems
useful).
I left a few strncpy() calls alone in the src/timezone/ code, to keep it
in sync with upstream (the IANA tzcode distribution). There are also a
few such calls in ecpg that could possibly do with more analysis.
AFAICT, none of these changes are more than cosmetic, except for the four
occurrences in fe-secure-openssl.c, which are in fact buggy: an overlength
source leads to a non-null-terminated destination buffer and ensuing
misbehavior. These don't seem like security issues, first because no stack
clobber is possible and second because if your values of sslcert etc are
coming from untrusted sources then you've got problems way worse than this.
Still, it's undesirable to have unpredictable behavior for overlength
inputs, so back-patch those four changes to all active branches.
2015-01-24 13:05:42 -05:00
|
|
|
StrNCpy(NameStr(*n1), NameStr(*n2), NAMEDATALEN);
|
1998-09-01 03:29:17 +00:00
|
|
|
return 0;
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
1997-08-19 21:40:56 +00:00
|
|
|
#ifdef NOT_USED
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
|
|
|
namecat(Name n1, Name n2)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
2005-10-15 02:49:52 +00:00
|
|
|
return namestrcat(n1, NameStr(*n2)); /* n2 can't be any longer than
|
|
|
|
* n1 */
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
1997-08-19 21:40:56 +00:00
|
|
|
#endif
|
1996-07-09 06:22:35 +00:00
|
|
|
|
1998-10-08 18:30:52 +00:00
|
|
|
#ifdef NOT_USED
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
|
|
|
namecmp(Name n1, Name n2)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
1999-11-07 23:08:36 +00:00
|
|
|
return strncmp(NameStr(*n1), NameStr(*n2), NAMEDATALEN);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
1998-10-08 18:30:52 +00:00
|
|
|
#endif
|
1996-07-09 06:22:35 +00:00
|
|
|
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
2000-01-22 14:20:56 +00:00
|
|
|
namestrcpy(Name name, const char *str)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
1997-09-07 05:04:48 +00:00
|
|
|
if (!name || !str)
|
1998-09-01 03:29:17 +00:00
|
|
|
return -1;
|
1999-11-07 23:08:36 +00:00
|
|
|
StrNCpy(NameStr(*name), str, NAMEDATALEN);
|
1998-09-01 03:29:17 +00:00
|
|
|
return 0;
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
1997-08-19 21:40:56 +00:00
|
|
|
#ifdef NOT_USED
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
2000-01-22 14:20:56 +00:00
|
|
|
namestrcat(Name name, const char *str)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
1997-09-08 02:41:22 +00:00
|
|
|
int i;
|
|
|
|
char *p,
|
|
|
|
*q;
|
1997-09-07 05:04:48 +00:00
|
|
|
|
|
|
|
if (!name || !str)
|
1998-09-01 03:29:17 +00:00
|
|
|
return -1;
|
1999-11-07 23:08:36 +00:00
|
|
|
for (i = 0, p = NameStr(*name); i < NAMEDATALEN && *p; ++i, ++p)
|
1997-09-07 05:04:48 +00:00
|
|
|
;
|
|
|
|
for (q = str; i < NAMEDATALEN; ++i, ++p, ++q)
|
|
|
|
{
|
|
|
|
*p = *q;
|
|
|
|
if (!*q)
|
|
|
|
break;
|
|
|
|
}
|
1998-09-01 03:29:17 +00:00
|
|
|
return 0;
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
1997-08-19 21:40:56 +00:00
|
|
|
#endif
|
1996-07-09 06:22:35 +00:00
|
|
|
|
1997-09-07 05:04:48 +00:00
|
|
|
int
|
2000-01-22 14:20:56 +00:00
|
|
|
namestrcmp(Name name, const char *str)
|
1996-07-09 06:22:35 +00:00
|
|
|
{
|
1997-09-07 05:04:48 +00:00
|
|
|
if (!name && !str)
|
1998-09-01 03:29:17 +00:00
|
|
|
return 0;
|
1997-09-07 05:04:48 +00:00
|
|
|
if (!name)
|
1998-09-01 04:40:42 +00:00
|
|
|
return -1; /* NULL < anything */
|
1997-09-07 05:04:48 +00:00
|
|
|
if (!str)
|
1998-09-01 03:29:17 +00:00
|
|
|
return 1; /* NULL < anything */
|
1999-11-07 23:08:36 +00:00
|
|
|
return strncmp(NameStr(*name), str, NAMEDATALEN);
|
1996-07-09 06:22:35 +00:00
|
|
|
}
|
|
|
|
|
2000-09-19 18:18:04 +00:00
|
|
|
|
2002-04-26 01:24:08 +00:00
|
|
|
/*
|
|
|
|
* SQL-functions CURRENT_USER, SESSION_USER
|
|
|
|
*/
|
2000-09-19 18:18:04 +00:00
|
|
|
Datum
|
|
|
|
current_user(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2002-06-11 13:40:53 +00:00
|
|
|
PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetUserId()))));
|
2000-09-19 18:18:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
session_user(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2002-06-11 13:40:53 +00:00
|
|
|
PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId()))));
|
2000-09-19 18:18:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-04-26 01:24:08 +00:00
|
|
|
/*
|
|
|
|
* SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
|
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
current_schema(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2002-09-04 20:31:48 +00:00
|
|
|
List *search_path = fetch_search_path(false);
|
|
|
|
char *nspname;
|
2002-04-26 01:24:08 +00:00
|
|
|
|
|
|
|
if (search_path == NIL)
|
|
|
|
PG_RETURN_NULL();
|
2004-05-26 04:41:50 +00:00
|
|
|
nspname = get_namespace_name(linitial_oid(search_path));
|
|
|
|
list_free(search_path);
|
2003-04-27 23:22:13 +00:00
|
|
|
if (!nspname)
|
|
|
|
PG_RETURN_NULL(); /* recently-deleted namespace? */
|
2002-04-26 01:24:08 +00:00
|
|
|
PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(nspname)));
|
|
|
|
}
|
|
|
|
|
|
|
|
Datum
|
|
|
|
current_schemas(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2002-09-04 20:31:48 +00:00
|
|
|
List *search_path = fetch_search_path(PG_GETARG_BOOL(0));
|
2004-05-26 04:41:50 +00:00
|
|
|
ListCell *l;
|
2002-09-04 20:31:48 +00:00
|
|
|
Datum *names;
|
|
|
|
int i;
|
|
|
|
ArrayType *array;
|
2002-04-26 01:24:08 +00:00
|
|
|
|
2004-06-05 19:48:09 +00:00
|
|
|
names = (Datum *) palloc(list_length(search_path) * sizeof(Datum));
|
2002-04-26 01:24:08 +00:00
|
|
|
i = 0;
|
2004-05-26 04:41:50 +00:00
|
|
|
foreach(l, search_path)
|
2002-04-26 01:24:08 +00:00
|
|
|
{
|
2002-09-04 20:31:48 +00:00
|
|
|
char *nspname;
|
2002-04-26 01:24:08 +00:00
|
|
|
|
2004-05-26 04:41:50 +00:00
|
|
|
nspname = get_namespace_name(lfirst_oid(l));
|
2003-04-27 23:22:13 +00:00
|
|
|
if (nspname) /* watch out for deleted namespace */
|
|
|
|
{
|
|
|
|
names[i] = DirectFunctionCall1(namein, CStringGetDatum(nspname));
|
|
|
|
i++;
|
|
|
|
}
|
2002-04-26 01:24:08 +00:00
|
|
|
}
|
2004-05-26 04:41:50 +00:00
|
|
|
list_free(search_path);
|
2002-04-26 01:24:08 +00:00
|
|
|
|
2003-04-27 23:22:13 +00:00
|
|
|
array = construct_array(names, i,
|
2002-08-26 17:54:02 +00:00
|
|
|
NAMEOID,
|
2002-09-04 20:31:48 +00:00
|
|
|
NAMEDATALEN, /* sizeof(Name) */
|
|
|
|
false, /* Name is not by-val */
|
Reduce the alignment requirement of type "name" from int to char, and arrange
to suppress zero-padding of "name" entries in indexes.
The alignment change is unlikely to save any space, but it is really needed
anyway to make the world safe for our widespread practice of passing plain
old C strings to functions that are declared as taking Name. In the previous
coding, the C compiler was entitled to assume that a Name pointer was
word-aligned; but we were failing to guarantee that. I think the reason
we'd not seen failures is that usually the only thing that gets done with
such a pointer is strcmp(), which is hard to optimize in a way that exploits
word-alignment. Still, some enterprising compiler guy will probably think
of a way eventually, or we might change our code in a way that exposes
more-obvious optimization opportunities.
The padding change is accomplished in one-liner fashion by declaring the
"name" index opclasses to use storage type "cstring" in pg_opclass.h.
Normally btree and hash don't allow a nondefault storage type, because they
don't have any provisions for converting the input datum to another type.
However, because name and cstring are effectively the same thing except for
padding, no conversion is needed --- we only need index_form_tuple() to treat
the datum as being cstring not name, and this is sufficient. This seems to
make for about a one-third reduction in the typical sizes of system catalog
indexes that involve "name" columns, of which we have many.
These two changes are only weakly related, but the alignment change makes
me feel safer that the padding change won't introduce problems, so I'm
committing them together.
2008-06-24 17:58:27 +00:00
|
|
|
'c'); /* alignment of Name */
|
2002-04-26 01:24:08 +00:00
|
|
|
|
|
|
|
PG_RETURN_POINTER(array);
|
|
|
|
}
|