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 <tomas.von...@enterprisedb.com>
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);
}




Reply via email to