I played around array_func.c
many of the code can be used for multiset data type.
now I imagine multiset as something like one dimension array. (nested is
somehow beyond the imagination...).
* A standard varlena array has the following internal structure:
* <vl_len_> - standard varlena header word
* <ndim> - number of dimensions of the array
* <dataoffset> - offset to stored data, or 0 if no nulls bitmap
* <elemtype> - element type OID
* <dimensions> - length of each array axis (C array of int)
* <lower bnds> - lower boundary of each dimension (C array of int)
* <null bitmap> - bitmap showing locations of nulls (OPTIONAL)
* <actual data> - whatever is the stored data
in set/multiset, we don't need {ndim,lower bnds}, since we are only one
dimension, also we don't need subscript.
So for set we can have following
* int32 vl_len_; /* varlena header (do not touch directly!) */
* int32 capacity; /* # of capacity */
* int32 dataoffset; /* offset to data, or 0 if no bitmap */
* int32 nelements; /* number of items added to the hashset */
* Oid elemtype; /* element type OID */
* <null bitmap> - bitmap showing locations of nulls (OPTIONAL)
* <bitmap> - bitmap showing this slot is empty or not ( I am not sure
this part)
* <actual data> - whatever is the stored data
many of the code in array_func.c can be reused.
array_isspace ==> set_isspace
ArrayMetaState ==> SetMetastate
ArrayCount ==> SetCount (similar to ArrayCount return the dimension
of set, should be zero (empty set) or one)
ArrayParseState ==> SetParseState
ReadArrayStr ==> ReadSetStr
attached is a demo shows that use array_func.c to parse cstring. have
similar effect of array_in.
for multiset_in set type input function. if no duplicate required then
multiset_in would just like array, so more code can be copied from
array_func.c
but if unique required then we need first palloc0(capacity * datums (size
per type)) then put valid value into to a specific slot?
On Fri, Jun 23, 2023 at 6:27 AM Tomas Vondra <[email protected]>
wrote:
> On 6/22/23 19:52, Joel Jacobson wrote:
> > On Tue, Jun 20, 2023, at 14:10, Tomas Vondra wrote:
> >> This is also what the SQL standard does for multisets - there's SQL:20nn
> >> draft at http://www.wiscorp.com/SQLStandards.html, and the <member
> >> predicate> section (p. 475) explains how this should work with NULL.
> >
> > I've looked again at the paper you mentioned and found something
> intriguing
> > in section 2.6 (b). I'm a bit puzzled about this: why would we want to
> return
> > null when we're certain it's not null but just doesn't have any elements?
> >
> > In the same vein, it says, "If it has more than one element, an
> exception is
> > raised." Makes sense to me, but what about when there are no elements at
> all?
> > Why not raise an exception in that case too?
> >
> > The ELEMENT function is designed to do one simple thing: return the
> element of
> > a multiset if the multiset has only 1 element. This seems very similar
> to how
> > our INTO STRICT operates, right?
> >
>
> I agree this looks a bit weird, but that's what I mentioned - this is an
> initial a proposal, outlining the idea. Inevitably some of the stuff
> will get reworked or just left out of the final version. It's useful
> mostly to explain the motivation / goal.
>
> I believe that's the case here - I don't think the ELEMENT got into the
> standard at all, and the NULL rules for the MEMBER OF clause seem not to
> have these strange bits.
>
> > The SQL:20nn seems to still be in draft form, and I can't help but
> wonder if we
> > should propose a bit of an improvement here:
> >
> > "If it doesn't have exactly one element, an exception is raised."
> >
> > Meaning, it would raise an exception both if there are more elements,
> > or zero elements (no elements).
> >
> > I think this would make the semantics more intuitive and less surprising.
> >
>
> Well, the simple truth is the draft is freely available, but you'd need
> to buy the final version. It doesn't mean it's still being worked on or
> that no SQL standard was released since then. In fact, SQL 2023 was
> released a couple weeks ago [1].
>
> It'd be interesting to know the version that actually got into the SQL
> standard (if at all), but I don't have access to the standard yet.
>
> regards
>
>
> [1] https://www.iso.org/standard/76584.html
>
> --
> Tomas Vondra
> EnterpriseDB: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
--
I recommend David Deutsch's <<The Beginning of Infinity>>
Jian
/*
gcc -I/home/jian/postgres/2023_05_25_beta5421/include/server -fPIC -c /home/jian/Desktop/regress_pgsql/set.c
gcc -shared -o /home/jian/Desktop/regress_pgsql/set.so /home/jian/Desktop/regress_pgsql/set.o
CREATE OR REPLACE FUNCTION set_in_test(cstring,int, int) RETURNS BOOL SET search_path from current
AS '/home/jian/Desktop/regress_pgsql/set', 'set_in_test'
LANGUAGE C IMMUTABLE;
select set_in_test('{1,2,3,NULL,NULL, NULL}',23,-1);
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
// #include "utils/array.h"
#include "utils/numeric.h"
#include "funcapi.h"
#include "utils/lsyscache.h"
#include "utils/fmgrprotos.h"
#include "common/hashfn.h"
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(set_in_test);
#define CEIL_DIV(a, b) (((a) + (b) - 1) / (b))
/*
* Arrays are varlena objects, so must meet the varlena convention that
* the first int32 of the object contains the total object size in bytes.
* Be sure to use VARSIZE() and SET_VARSIZE() to access it, though!
*
* CAUTION: if you change the header for ordinary arrays you will also
* need to change the headers for oidvector and int2vector!
*/
typedef struct SetType
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int32 capacity; /* # of capacity */
int32 dataoffset; /* offset to data, or 0 if no bitmap */
int32 nelements; /* number of items added to the hashset */
Oid elemtype; /* element type OID */
} SetType;
#define SET_SIZE(a) VARSIZE(a)
#define SET_ITEM(a) ((a)->nelements)
#define SET_CAPACITY(a) ((a)->capacity)
#define SET_HASNULL(a) ((a)->dataoffset != 0)
#define SET_ELEMTYPE(a) ((a)->elemtype)
#define SET_OVERHEAD_NONULLS(capacity) \
MAXALIGN(sizeof(SetType) + CEIL_DIV(capacity, 8))
#define SET_OVERHEAD_WITHNULLS(capacity,nelements) \
MAXALIGN(sizeof(SetType) + CEIL_DIV(capacity, 8) + \
((nelements) + 7) / 8)
#define SET_DATA_OFFSET(a) \
(SET_HASNULL(a) ? (a)->dataoffset : SET_OVERHEAD_NONULLS(SET_CAPACITY(a)))
#define SET_NULLBITMAP(a) \
(SET_HASNULL(a) ? \
(bits8 *) (((char *) (a)) + sizeof(SetType) + \
((a->nelements) + 7) / 8) \
: (bits8 *) NULL)
/*
* Returns a pointer to the actual array data.
*/
#define SET_DATA_PTR(a) \
(((char *) (a)) + SET_DATA_OFFSET(a))
typedef struct SetMetaState
{
Oid element_type;
int16 typlen;
bool typbyval;
char typalign;
char typdelim;
Oid typioparam;
Oid typiofunc;
FmgrInfo proc;
} SetMetaState;
static int
SetCount(const char *str, int *dim, char typdelim, Node *escontext);
static bool
set_get_isnull(const bits8 *nullbitmap, int offset);
/*
* Check whether a specific array element is NULL
*
* nullbitmap: pointer to array's null bitmap (NULL if none)
* offset: 0-based linear element number of array element
*/
static bool
set_get_isnull(const bits8 *nullbitmap, int offset)
{
if (nullbitmap == NULL)
return false; /* assume not null */
if (nullbitmap[offset / 8] & (1 << (offset % 8)))
return false; /* not null */
return true;
}
/*
* Copy datum to *dest and return total space used (including align padding)
*
* Caller must have handled case of NULL element
*/
static int
SetCastAndSet(Datum src,
int typlen,
bool typbyval,
char typalign,
char *dest)
{
int inc;
if (typlen > 0)
{
if (typbyval)
store_att_byval(dest, src, typlen);
else
memmove(dest, DatumGetPointer(src), typlen);
inc = att_align_nominal(typlen, typalign);
}
else
{
Assert(!typbyval);
inc = att_addlength_datum(0, typlen, src);
memmove(dest, DatumGetPointer(src), inc);
inc = att_align_nominal(inc, typalign);
}
return inc;
}
/*
* Copy data into an array object from a temporary array of Datums.
*
* array: array object (with header fields already filled in)
* values: array of Datums to be copied
* nulls: array of is-null flags (can be NULL if no nulls)
* nitems: number of Datums to be copied
* typbyval, typlen, typalign: info about element datatype
* freedata: if true and element type is pass-by-ref, pfree data values
* referenced by Datums after copying them.
*
* If the input data is of varlena type, the caller must have ensured that
* the values are not toasted. (Doing it here doesn't work since the
* caller has already allocated space for the array...)
*/
void
CopySetEls(SetType *array,
Datum *values,
bool *nulls,
int nitems,
int typlen,
bool typbyval,
char typalign,
bool freedata)
{
char *p = SET_DATA_PTR(array);
bits8 *bitmap = SET_NULLBITMAP(array);
int bitval = 0;
int bitmask = 1;
int i;
if (typbyval)
freedata = false;
for (i = 0; i < nitems; i++)
{
if (nulls && nulls[i])
{
if (!bitmap) /* shouldn't happen */
elog(ERROR, "null array element where not supported");
/* bitmap bit stays 0 */
}
else
{
bitval |= bitmask;
p += SetCastAndSet(values[i], typlen, typbyval, typalign, p);
if (freedata)
pfree(DatumGetPointer(values[i]));
}
if (bitmap)
{
bitmask <<= 1;
if (bitmask == 0x100)
{
*bitmap++ = bitval;
bitval = 0;
bitmask = 1;
}
}
}
if (bitmap && bitmask != 1)
*bitmap = bitval;
}
/*
* array_isspace() --- a non-locale-dependent isspace()
*
* We used to use isspace() for parsing array values, but that has
* undesirable results: an array value might be silently interpreted
* differently depending on the locale setting. Now we just hard-wire
* the traditional ASCII definition of isspace().
*/
static bool
set_isspace(char ch)
{
if (ch == ' ' ||
ch == '\t' ||
ch == '\n' ||
ch == '\r' ||
ch == '\v' ||
ch == '\f')
return true;
return false;
}
/*
* ReadSetStr :
* parses the array string pointed to by "arrayStr" and converts the values
* to internal format. Unspecified elements are initialized to nulls.
* The array dimensions must already have been determined.
*
* Inputs:
* arrayStr: the string to parse.
* CAUTION: the contents of "arrayStr" will be modified!
* origStr: the unmodified input string, used only in error messages.
* nitems: total number of array elements, as already determined.
* ndim: number of array dimensions
* dim[]: array axis lengths
* inputproc: type-specific input procedure for element datatype.
* typioparam, typmod: auxiliary values to pass to inputproc.
* typdelim: the value delimiter (type-specific).
* typlen, typbyval, typalign: storage parameters of element datatype.
*
* Outputs:
* values[]: filled with converted data values.
* nulls[]: filled with is-null markers.
* *hasnulls: set true iff there are any null elements.
* *nbytes: set to total size of data area needed (including alignment
* padding but not including array header overhead).
* *escontext: if this points to an ErrorSaveContext, details of
* any error are reported there.
*
* Result:
* true for success, false for failure (if escontext is provided).
*
* Note that values[] and nulls[] are allocated by the caller, and must have
* nitems elements.
*/
typedef enum
{
SET_NO_LEVEL,
SET_LEVEL_STARTED,
SET_ELEM_STARTED,
SET_ELEM_COMPLETED,
SET_QUOTED_ELEM_STARTED,
SET_QUOTED_ELEM_COMPLETED,
SET_ELEM_DELIMITED,
SET_LEVEL_COMPLETED,
SET_LEVEL_DELIMITED
} SetParseState;
/*
* SetCount
* Determines the dimensions for an array string.
*
* Returns number of dimensions as function result. The axis lengths are
* returned in dim[], which must be of size MAXDIM.
*
* If we detect an error, fill *escontext with error details and return -1
* (unless escontext isn't provided, in which case errors will be thrown).
*/
#undef MAXDIM
#define MAXDIM 1
static int
SetCount(const char *str, int *dim, char typdelim, Node *escontext)
{
int nest_level = 0,
i;
int ndim = 1,
temp[MAXDIM],
nelems[MAXDIM],
nelems_last[MAXDIM];
bool in_quotes = false;
bool eoArray = false;
bool empty_array = true;
const char *ptr;
SetParseState parse_state = SET_NO_LEVEL;
for (i = 0; i < MAXDIM; ++i)
{
temp[i] = dim[i] = nelems_last[i] = 0;
nelems[i] = 1;
}
ptr = str;
while (!eoArray)
{
bool itemdone = false;
while (!itemdone)
{
if (parse_state == SET_ELEM_STARTED ||
parse_state == SET_QUOTED_ELEM_STARTED)
empty_array = false;
switch (*ptr)
{
case '\0':
/* Signal a premature end of the string */
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected end of input.")));
case '\\':
/*
* An escape must be after a level start, after an element
* start, or after an element delimiter. In any case we
* now must be past an element start.
*/
if (parse_state != SET_LEVEL_STARTED &&
parse_state != SET_ELEM_STARTED &&
parse_state != SET_QUOTED_ELEM_STARTED &&
parse_state != SET_ELEM_DELIMITED)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected \"%c\" character.",
'\\')));
if (parse_state != SET_QUOTED_ELEM_STARTED)
parse_state = SET_ELEM_STARTED;
/* skip the escaped character */
if (*(ptr + 1))
ptr++;
else
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected end of input.")));
break;
case '"':
/*
* A quote must be after a level start, after a quoted
* element start, or after an element delimiter. In any
* case we now must be past an element start.
*/
if (parse_state != SET_LEVEL_STARTED &&
parse_state != SET_QUOTED_ELEM_STARTED &&
parse_state != SET_ELEM_DELIMITED)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected array element.")));
in_quotes = !in_quotes;
if (in_quotes)
parse_state = SET_QUOTED_ELEM_STARTED;
else
parse_state = SET_QUOTED_ELEM_COMPLETED;
break;
case '{':
if (!in_quotes)
{
/*
* A left brace can occur if no nesting has occurred
* yet, after a level start, or after a level
* delimiter.
*/
if (parse_state != SET_NO_LEVEL &&
parse_state != SET_LEVEL_STARTED &&
parse_state != SET_LEVEL_DELIMITED)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected \"%c\" character.",
'{')));
parse_state = SET_LEVEL_STARTED;
if (nest_level >= MAXDIM)
ereturn(escontext, -1,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
nest_level + 1, MAXDIM)));
temp[nest_level] = 0;
nest_level++;
if (ndim < nest_level)
ndim = nest_level;
}
break;
case '}':
if (!in_quotes)
{
/*
* A right brace can occur after an element start, an
* element completion, a quoted element completion, or
* a level completion.
*/
if (parse_state != SET_ELEM_STARTED &&
parse_state != SET_ELEM_COMPLETED &&
parse_state != SET_QUOTED_ELEM_COMPLETED &&
parse_state != SET_LEVEL_COMPLETED &&
!(nest_level == 1 && parse_state == SET_LEVEL_STARTED))
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected \"%c\" character.",
'}')));
parse_state = SET_LEVEL_COMPLETED;
if (nest_level == 0)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unmatched \"%c\" character.", '}')));
nest_level--;
if (nelems_last[nest_level] != 0 &&
nelems[nest_level] != nelems_last[nest_level])
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Multidimensional arrays must have "
"sub-arrays with matching "
"dimensions.")));
nelems_last[nest_level] = nelems[nest_level];
nelems[nest_level] = 1;
if (nest_level == 0)
eoArray = itemdone = true;
else
{
/*
* We don't set itemdone here; see comments in
* ReadSetStr
*/
temp[nest_level - 1]++;
}
}
break;
default:
if (!in_quotes)
{
if (*ptr == typdelim)
{
/*
* Delimiters can occur after an element start, an
* element completion, a quoted element
* completion, or a level completion.
*/
if (parse_state != SET_ELEM_STARTED &&
parse_state != SET_ELEM_COMPLETED &&
parse_state != SET_QUOTED_ELEM_COMPLETED &&
parse_state != SET_LEVEL_COMPLETED)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected \"%c\" character.",
typdelim)));
if (parse_state == SET_LEVEL_COMPLETED)
parse_state = SET_LEVEL_DELIMITED;
else
parse_state = SET_ELEM_DELIMITED;
itemdone = true;
nelems[nest_level - 1]++;
}
else if (!set_isspace(*ptr))
{
/*
* Other non-space characters must be after a
* level start, after an element start, or after
* an element delimiter. In any case we now must
* be past an element start.
*/
if (parse_state != SET_LEVEL_STARTED &&
parse_state != SET_ELEM_STARTED &&
parse_state != SET_ELEM_DELIMITED)
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Unexpected array element.")));
parse_state = SET_ELEM_STARTED;
}
}
break;
}
if (!itemdone)
ptr++;
}
temp[ndim - 1]++;
ptr++;
}
/* only whitespace is allowed after the closing brace */
while (*ptr)
{
if (!set_isspace(*ptr++))
ereturn(escontext, -1,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"", str),
errdetail("Junk after closing right brace.")));
}
/* special case for an empty array */
if (empty_array)
return 0;
for (i = 0; i < ndim; ++i)
dim[i] = temp[i];
return ndim;
}
bool Array_nulls = true;
static bool
ReadSetStr(char *arrayStr,
const char *origStr,
int nitems,
int ndim,
int *dim,
FmgrInfo *inputproc,
Oid typioparam,
int32 typmod,
char typdelim,
int typlen,
bool typbyval,
char typalign,
Datum *values,
bool *nulls,
bool *hasnulls,
int32 *nbytes,
Node *escontext)
{
int i;
char *srcptr;
bool in_quotes = false;
bool eoArray = false;
bool hasnull;
int32 totbytes;
int indx = 0;
/* Initialize is-null markers to true */
memset(nulls, true, nitems * sizeof(bool));
/*
* We have to remove " and \ characters to create a clean item value to
* pass to the datatype input routine. We overwrite each item value
* in-place within arrayStr to do this. srcptr is the current scan point,
* and dstptr is where we are copying to.
*
* We also want to suppress leading and trailing unquoted whitespace. We
* use the leadingspace flag to suppress leading space. Trailing space is
* tracked by using dstendptr to point to the last significant output
* character.
*
* The error checking in this routine is mostly pro-forma, since we expect
* that SetCount() already validated the string. So we don't bother
* with errdetail messages.
*/
srcptr = arrayStr;
while (!eoArray)
{
bool itemdone = false;
bool leadingspace = true;
bool hasquoting = false;
char *itemstart;
char *dstptr;
char *dstendptr;
itemstart = dstptr = dstendptr = srcptr;
while (!itemdone)
{
switch(*srcptr)
{
case '\0':
/* Signal a premature end of the string */
/* Signal a premature end of the string */
ereturn(escontext, false,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"",
origStr)));
break;
case '\\':
/* Skip backslash, copy next character as-is. */
srcptr++;
if (*srcptr == '\0')
ereturn(escontext,false,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("malformed array literal: \"%s\"",
origStr)));
/* Treat the escaped character as non-whitespace*/
leadingspace = false;
dstendptr = dstptr;
hasquoting = false; /* can't be a NULL marker */
break;
case '"':
in_quotes = !in_quotes;
if (in_quotes)
leadingspace = false;
else
{
/*
* Advance dstendptr when we exit in_quotes; this
* saves having to do it in all the other in_quotes
* cases.
*/
dstendptr = dstptr;
}
hasquoting = true; /* can't be a NULL marker */
srcptr++;
break;
case '{':
if (!in_quotes)
{
srcptr++;
}
else
*dstptr++ = *srcptr++;
break;
case '}':
if (!in_quotes)
{
eoArray = itemdone = true;
srcptr++;
}
else
*dstptr++ = *srcptr++;
break;
default :
if(in_quotes)
*dstptr++ = *srcptr++;
else if (*srcptr == typdelim)
{
itemdone = true;
srcptr ++;
}
else if (set_isspace(*srcptr))
{
/*
* If leading space, drop it immediately. Else, copy
* but don't advance dstendptr.
*/
if(leadingspace)
srcptr++;
else
*dstptr++ = *srcptr++;
}
else
{
*dstptr++ = *srcptr++;
leadingspace = false;
dstendptr = dstptr;
}
break;
}
}
Assert(dstptr < srcptr);
*dstendptr = '\0';
if (Array_nulls && !hasquoting &&
pg_strcasecmp(itemstart, "NULL") == 0)
{
/* it's a NULL item */
if (!InputFunctionCallSafe(inputproc, NULL,
typioparam, typmod,
escontext,
&values[indx]))
return false;
nulls[indx] = true;
indx++;
}
else
{
elog(INFO,"line %d indx:%d itemstart:%s, typioparam:%d,typmod:%d",__LINE__,indx,itemstart, typioparam,typmod);
if (!InputFunctionCallSafe(inputproc, itemstart,
typioparam, typmod,
escontext,
&values[indx]))
return false;
nulls[indx] = false;
indx++;
}
}
/*
* Check for nulls, compute total data space needed
*/
hasnull = false;
totbytes = 0;
elog(INFO,"line[%03d]nitems=%d, dim=%d",__LINE__,nitems,*dim);
for (i = 0; i < nitems; i++)
{
if(nulls[i])
hasnull = true;
else
{
/* let's just make sure data is not toasted */
if (typlen == -1)
values[i] = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
totbytes = att_addlength_datum(totbytes, typlen, values[i]);
totbytes = att_align_nominal(totbytes, typalign);
elog(INFO,"line[%03d] total bytes: %d",__LINE__,totbytes);
/* check for overflow of total request */
if (!AllocSizeIsValid(totbytes))
ereturn(escontext, false,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("array size exceed the maximum allowed (%d)",
(int) MaxAllocSize)));
}
}
*hasnulls = hasnull;
*nbytes = totbytes;
elog(INFO,"total bytes: %d, hasnulls:%d",totbytes, *hasnulls);
return true;
}
//imitate array_in.
Datum
set_in_test(PG_FUNCTION_ARGS)
{
char *string = PG_GETARG_CSTRING(0); /* external form */
Oid element_type = PG_GETARG_OID(1); /* type of an array * element */
int32 typmod = PG_GETARG_INT32(2); /* typmod for array elements */
Node *escontext = fcinfo->context;
int typlen;
bool typbyval;
char typalign;
char typdelim = ',';
Oid typioparam;
char *string_save,
*p;
int nitems;
Datum *dataPtr;
bool *nullsPtr;
bool hasnulls;
int32 nbytes;
int32 dataoffset;
SetType *retval;
int dim;
int ndim;
SetMetaState *my_extra;
/*
* We arrange to look up info about element type, including its input
* conversion proc, only once per series of calls, assuming the element
* type doesn't change underneath us.
*/
my_extra = (SetMetaState *) fcinfo->flinfo->fn_extra;
if (my_extra == NULL)
{
fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt
,sizeof(SetMetaState));
my_extra = (SetMetaState *) fcinfo->flinfo->fn_extra;
my_extra->element_type = ~element_type;
}
if (my_extra->element_type != element_type)
{
/*
* Get info about element type, including its input conversion proc
*/
get_type_io_data(element_type,IOFunc_input,
&my_extra->typlen,&my_extra->typbyval,
&my_extra->typalign, &my_extra->typdelim,
&my_extra->typioparam,&my_extra->typiofunc);
fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc,
fcinfo->flinfo->fn_mcxt);
my_extra->element_type = element_type;
}
typlen = my_extra->typlen;
typbyval = my_extra->typbyval;
typalign = my_extra->typalign;
typdelim = my_extra->typdelim;
typioparam = my_extra->typioparam;
elog(INFO,"typlen: %d, typbyval:%d typalign %c typdelim %c typioparam %d"
,typlen,typbyval, typalign, typdelim, typioparam);
ndim = SetCount(string,&dim,typdelim, escontext);
// ndim eith zero or 1. So plan accordingly.
nitems = dim;
dataPtr = (Datum *) palloc(nitems * sizeof(Datum));
nullsPtr = (bool *) palloc(nitems * sizeof(bool));
/* Make a modifiable copy of the input */
string_save = pstrdup(string);
/*
* If the input string starts with dimension info, read and use that.
* Otherwise, we require the input to be in curly-brace style, and we
* prescan the input to determine dimensions.
*
* Dimension info takes the form of one or more [n] or [m:n] items. The
* outer loop iterates once per dimension item.
*/
p = string_save;
elog(INFO,"line %d, function oid : %d",__LINE__,*(&my_extra->proc.fn_oid));
Assert(nitems == dim);
if (!ReadSetStr(p, string,
nitems, ndim, &dim,
&my_extra->proc, typioparam, typmod,
typdelim,
typlen, typbyval, typalign,
dataPtr, nullsPtr,
&hasnulls, &nbytes, escontext))
{
elog(INFO,"ReadSetStr FALSE, nbytes %d, hasnulls %d dim:%d",nbytes,hasnulls,dim);
PG_RETURN_BOOL(false);
}
elog(INFO,"ReadSetStr OK, nbytes %d, hasnulls %d dim:%d",nbytes,hasnulls,dim);
int capacity =64;
if (hasnulls)
{
dataoffset = SET_OVERHEAD_WITHNULLS(capacity, nitems);
elog(INFO,"line %3d SET_OVERHEAD_WITHNULLS :%ld",__LINE__,SET_OVERHEAD_WITHNULLS(capacity, nitems));
nbytes += dataoffset;
}
else
{
dataoffset = 0;
elog(INFO,"LINE[%03d] SET_OVERHEAD_NONULLS: %ld",__LINE__,SET_OVERHEAD_NONULLS(capacity));
elog(INFO,"line %03d test %ld", __LINE__,MAXALIGN(sizeof(SetType) + CEIL_DIV(capacity, 8)));
nbytes += SET_OVERHEAD_NONULLS(capacity);
}
retval = (SetType *) palloc0(nbytes);
SET_VARSIZE(retval, nbytes);
elog(INFO,"LINE[%03d] nbytes = %d",__LINE__,nbytes);
Assert(ndim == 1); // or deal with ndim = 0;
retval->nelements = nitems;
retval->dataoffset = dataoffset;
retval->capacity = capacity;
/*
* This comes from the array's pg_type.typelem (which points to the base
* data type's pg_type.oid) and stores system oids in user tables. This
* oid must be preserved by binary upgrades.
*/
retval->elemtype = element_type;
elog(INFO,"line [%03d] returning array retval->elemtype=:%d",__LINE__,SET_ELEMTYPE(retval));
for (int j = 0; j < retval->nelements; j ++)
{
elog(INFO,"[%03d] test nullptr: %d", j,*(nullsPtr+j));
}
CopySetEls(retval,
dataPtr, nullsPtr, nitems,
typlen, typbyval, typalign,
true);
if(SET_HASNULL(retval))
{
for (int j = 0; j < retval->nelements; j ++)
{
elog(INFO," line[%03d]: [%03d] after copy: %d",__LINE__,j,*(SET_NULLBITMAP(retval)+j));
}
}
pfree(dataPtr);
pfree(nullsPtr);
// PG_RETURN_ARRAYTYPE_P(retval);
PG_RETURN_BOOL(true);
}