diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 4b268f3..971be7a 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 453,458 ****
--- 453,473 ----
  }
  
  /*
+  * makeFuncCall -
+  *	build a FuncCall node
+  */
+ FuncCall *
+ makeFuncCall(List *funcname, List *args, int location)
+ {
+ 	FuncCall   *n = makeNode(FuncCall);
+ 
+ 	n->funcname = funcname;
+ 	n->args = args;
+ 	n->location = location;
+ 	return n;
+ }
+ 
+ /*
   * makeDefElem -
   *	build a DefElem node
   *
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d9b2074..70f3b68 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 463,469 ****
   */
  
  /* ordinary key words in alphabetical order */
! %token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
  	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
  	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
  
--- 463,469 ----
   */
  
  /* ordinary key words in alphabetical order */
! %token <keyword> A ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
  	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
  	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
  
*************** static RangeVar *makeRangeVarFromAnyName
*** 506,512 ****
  	LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
  	LOCATION LOCK_P LOGIN_P
  
! 	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
  	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
  	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
--- 506,512 ----
  	LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
  	LOCATION LOCK_P LOGIN_P
  
! 	MAPPING MATCH MAXVALUE MEMBER MINUTE_P MINVALUE MODE MONTH_P MOVE MULTISET
  
  	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NOCREATEDB
  	NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
*************** static RangeVar *makeRangeVarFromAnyName
*** 528,535 ****
  	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
  	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
  	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
! 	STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
! 	SYMMETRIC SYSID SYSTEM_P
  
  	TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
  	TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
--- 528,535 ----
  	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
  	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
  	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
! 	STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBMULTISET SUBSTRING
! 	SUPERUSER_P SYMMETRIC SYSID SYSTEM_P
  
  	TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
  	TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
*************** static RangeVar *makeRangeVarFromAnyName
*** 598,603 ****
--- 598,604 ----
  %nonassoc	NOTNULL
  %nonassoc	ISNULL
  %nonassoc	IS NULL_P TRUE_P FALSE_P UNKNOWN /* sets precedence for IS NULL, etc */
+ %nonassoc	MEMBER MULTISET SUBMULTISET
  %left		'+' '-'
  %left		'*' '/' '%'
  %left		'^'
*************** a_expr:		c_expr									{ $$ = $1; }
*** 9358,9363 ****
--- 9359,9428 ----
  														 list_make1($1), @2),
  											 @2);
  				}
+ 			| a_expr IS A SET
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("is_a_set"),
+ 							list_make1($1), @2);
+ 				}
+ 			| a_expr IS NOT A SET
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeFuncCall(
+ 								SystemFuncName("is_a_set"),
+ 								list_make1($1), @2), @2);
+ 				}
+ 			| a_expr MEMBER opt_of a_expr
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP_ANY,
+ 							"=", $1, $4, @2);
+ 				}
+ 			| a_expr NOT MEMBER opt_of a_expr
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP_ANY, "=",
+ 								$1, $5, @2), @2);
+ 				}
+ 			| a_expr SUBMULTISET opt_of a_expr
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("submultiset_of"),
+ 							list_make2($1, $4), @2);
+ 				}
+ 			| a_expr NOT SUBMULTISET opt_of a_expr
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeFuncCall(
+ 								SystemFuncName("submultiset_of"),
+ 								list_make2($1, $5), @2), @2);
+ 				}
+ 			| a_expr MULTISET UNION opt_all a_expr
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("multiset_union"),
+ 							list_make3($1, $5, makeBoolAConst($4, -1)), @2);
+ 				}
+ 			| a_expr MULTISET INTERSECT opt_all a_expr
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("multiset_intersect"),
+ 							list_make3($1, $5, makeBoolAConst($4, -1)), @2);
+ 				}
+ 			| a_expr MULTISET EXCEPT opt_all a_expr
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("multiset_except"),
+ 							list_make3($1, $5, makeBoolAConst($4, -1)), @2);
+ 				}
+ 		;
+ 
+ opt_of:		OF				{}
+ /*	FIXME: OF is an option in the SQL standard, but I cannot solve
+ 	shift/reduce errors without OF. To solve the errors, we might need
+ 	to make OF, MEMBER, and/or SUBMULTISET to reserved keywords. They
+ 	are reserved keywords in the SQL standard.
+ 			|				{}
+ */
  		;
  
  /*
*************** ColLabel:	IDENT									{ $$ = $1; }
*** 11073,11079 ****
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
--- 11138,11145 ----
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  A
! 			| ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
*************** unreserved_keyword:
*** 11200,11210 ****
--- 11266,11278 ----
  			| MAPPING
  			| MATCH
  			| MAXVALUE
+ 			| MEMBER
  			| MINUTE_P
  			| MINVALUE
  			| MODE
  			| MONTH_P
  			| MOVE
+ 			| MULTISET
  			| NAME_P
  			| NAMES
  			| NEXT
*************** unreserved_keyword:
*** 11290,11295 ****
--- 11358,11364 ----
  			| STORAGE
  			| STRICT_P
  			| STRIP_P
+ 			| SUBMULTISET
  			| SUPERUSER_P
  			| SYSID
  			| SYSTEM_P
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index d7ec310..1af9d06 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
***************
*** 15,21 ****
--- 15,26 ----
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/typcache.h"
  
+ static Datum array_cat_internal(PG_FUNCTION_ARGS, bool flatten);
+ static ArrayType *array_flatten_internal(ArrayType *array);
+ static void check_concatinatable(Oid element_type1, Oid element_type2);
+ static void check_comparable(Oid element_type1, Oid element_type2);
  
  /*-----------------------------------------------------------------------------
   * array_push :
*************** array_push(PG_FUNCTION_ARGS)
*** 168,173 ****
--- 173,184 ----
  Datum
  array_cat(PG_FUNCTION_ARGS)
  {
+ 	return array_cat_internal(fcinfo, false);
+ }
+ 
+ static Datum
+ array_cat_internal(PG_FUNCTION_ARGS, bool flatten)
+ {
  	ArrayType  *v1,
  			   *v2;
  	ArrayType  *result;
*************** array_cat(PG_FUNCTION_ARGS)
*** 203,213 ****
--- 214,228 ----
  		if (PG_ARGISNULL(1))
  			PG_RETURN_NULL();
  		result = PG_GETARG_ARRAYTYPE_P(1);
+ 		if (flatten)
+ 			result = array_flatten_internal(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  	if (PG_ARGISNULL(1))
  	{
  		result = PG_GETARG_ARRAYTYPE_P(0);
+ 		if (flatten)
+ 			result = array_flatten_internal(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  
*************** array_cat(PG_FUNCTION_ARGS)
*** 218,231 ****
  	element_type2 = ARR_ELEMTYPE(v2);
  
  	/* Check we have matching element types */
! 	if (element_type1 != element_type2)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATATYPE_MISMATCH),
! 				 errmsg("cannot concatenate incompatible arrays"),
! 				 errdetail("Arrays with element types %s and %s are not "
! 						   "compatible for concatenation.",
! 						   format_type_be(element_type1),
! 						   format_type_be(element_type2))));
  
  	/* OK, use it */
  	element_type = element_type1;
--- 233,239 ----
  	element_type2 = ARR_ELEMTYPE(v2);
  
  	/* Check we have matching element types */
! 	check_concatinatable(element_type1, element_type2);
  
  	/* OK, use it */
  	element_type = element_type1;
*************** array_cat(PG_FUNCTION_ARGS)
*** 249,261 ****
  	 * if both are empty, return the first one
  	 */
  	if (ndims1 == 0 && ndims2 > 0)
  		PG_RETURN_ARRAYTYPE_P(v2);
  
  	if (ndims2 == 0)
  		PG_RETURN_ARRAYTYPE_P(v1);
  
  	/* the rest fall under rule 3, 4, or 5 */
! 	if (ndims1 != ndims2 &&
  		ndims1 != ndims2 - 1 &&
  		ndims1 != ndims2 + 1)
  		ereport(ERROR,
--- 257,278 ----
  	 * if both are empty, return the first one
  	 */
  	if (ndims1 == 0 && ndims2 > 0)
+ 	{
+ 		if (flatten)
+ 			v2 = array_flatten_internal(v2);
  		PG_RETURN_ARRAYTYPE_P(v2);
+ 	}
  
  	if (ndims2 == 0)
+ 	{
+ 		if (flatten)
+ 			v1 = array_flatten_internal(v1);
  		PG_RETURN_ARRAYTYPE_P(v1);
+ 	}
  
  	/* the rest fall under rule 3, 4, or 5 */
! 	if (!flatten &&
! 		ndims1 != ndims2 &&
  		ndims1 != ndims2 - 1 &&
  		ndims1 != ndims2 + 1)
  		ereport(ERROR,
*************** array_cat(PG_FUNCTION_ARGS)
*** 279,285 ****
  	ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
  	ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
  
! 	if (ndims1 == ndims2)
  	{
  		/*
  		 * resulting array is made up of the elements (possibly arrays
--- 296,310 ----
  	ndatabytes1 = ARR_SIZE(v1) - ARR_DATA_OFFSET(v1);
  	ndatabytes2 = ARR_SIZE(v2) - ARR_DATA_OFFSET(v2);
  
! 	if (flatten)
! 	{
! 		ndims = 1;
! 		dims = (int *) palloc(sizeof(int));
! 		lbs = (int *) palloc(sizeof(int));
! 		dims[0] = nitems1 + nitems2;
! 		lbs[0] = 1;
! 	}
! 	else if (ndims1 == ndims2)
  	{
  		/*
  		 * resulting array is made up of the elements (possibly arrays
*************** array_agg_finalfn(PG_FUNCTION_ARGS)
*** 544,546 ****
--- 569,1466 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		returns the number of elements in the array.
+  */
+ Datum
+ array_cardinality(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
+ 	int			nitems;
+ 
+ 	nitems = ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v));
+ 
+ 	PG_RETURN_INT32(nitems);
+ }
+ 
+ /*
+  * trim_array :
+  *		remove elements at end of the array. Multi-dimensional array is
+  *		flattened into one-dimensional array.
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v = PG_GETARG_ARRAYTYPE_P(0);
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 	Datum	   *elems;
+ 	bool	   *nulls;
+ 	ArrayType  *result;
+ 	int			nitems;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("number of trimmed elements must not be negative")));
+ 
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	deconstruct_array(v, elmtype, elmlen, elmbyval, elmalign,
+ 					  &elems, &nulls, &nitems);
+ 
+ 	if (nitems <= ntrimmed)
+ 		result = construct_empty_array(elmtype);
+ 	else
+ 	{
+ 		int		dims = nitems - ntrimmed;
+ 		int		lbs = 1;
+ 
+ 		result = construct_md_array(elems, nulls, 1, &dims, &lbs,
+ 									elmtype, elmlen, elmbyval, elmalign);
+ 	}
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * Find TypeCacheEntry with comparison functions for element_type.
+  * We arrange to look up the compare functions only once per series of
+  * calls, assuming the element type doesn't change underneath us.
+  */
+ static TypeCacheEntry *
+ get_type_cache(Oid element_type, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 
+ 	type = (TypeCacheEntry *) *fn_extra;
+ 	if (type == NULL ||
+ 		type->type_id != element_type)
+ 	{
+ 		type = lookup_type_cache(element_type,
+ 					TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO);
+ 		if (!OidIsValid(type->eq_opr_finfo.fn_oid) ||
+ 			!OidIsValid(type->cmp_proc_finfo.fn_oid))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+ 			   errmsg("could not identify comparison functions for type %s",
+ 					  format_type_be(element_type))));
+ 		*fn_extra = type;
+ 	}
+ 
+ 	return type;
+ }
+ 
+ static int
+ compare_element(const void *a, const void *b, void *arg)
+ {
+ 	FunctionCallInfo fn = (FunctionCallInfo) arg;
+ 
+ 	fn->arg[0] = *(const Datum *) a;
+ 	fn->arg[1] = *(const Datum *) b;
+ 	fn->argnull[0] = false;
+ 	fn->argnull[1] = false;
+ 	fn->isnull = false;
+ 	return DatumGetInt32(FunctionCallInvoke(fn));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements.
+  */
+ static int
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls, int nitems)
+ {
+ 	int			nonnulls,
+ 				i;
+ 	FunctionCallInfoData fn;
+ 
+ 	if (nulls == NULL)
+ 		nonnulls = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = nonnulls = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[nonnulls] = values[i];
+ 				nulls[nonnulls] = false;
+ 				nonnulls++;
+ 			}
+ 		}
+ 		for (i = nonnulls; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	InitFunctionCallInfoData(fn, &type->cmp_proc_finfo, 2, NULL, NULL);
+ 	qsort_arg(values, nonnulls, sizeof(Datum), compare_element, &fn);
+ 
+ 	return nonnulls;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. Return the number
+  * of distinct elements. Note that there will be only one null value in
+  * the result even if there are some nulls.
+  */
+ static int
+ unique_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 				int nitems, int nonnulls)
+ {
+ 	int		i,
+ 			n;
+ 	FunctionCallInfoData fn;
+ 
+ 	InitFunctionCallInfoData(fn, &type->eq_opr_finfo, 2, NULL, NULL);
+ 
+ 	for (i = n = 1; i < nonnulls; i++)
+ 	{
+ 		fn.arg[0] = values[i - 1];
+ 		fn.arg[1] = values[i];
+ 		fn.argnull[0] = false;
+ 		fn.argnull[1] = false;
+ 		fn.isnull = false;
+ 		if (!DatumGetBool(FunctionCallInvoke(&fn)))
+ 			values[n++] = values[i];
+ 	}
+ 	if (nonnulls < nitems)
+ 		nulls[n++] = true;
+ 
+ 	return n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements, sort them, and optinally
+  * remove duplicated values in them. Returns number of non-null elements.
+  */
+ static int
+ deconstruct_and_sort(ArrayType *array, bool unique,
+ 					 Datum **values, bool **nulls, int *nitems,
+ 					 void **fn_extra)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	int			nonnulls;
+ 	TypeCacheEntry *type;
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, nulls, nitems);
+ 
+ 	nonnulls = sort_elements(type, *values, nulls ? *nulls : NULL, *nitems);
+ 	if (unique)
+ 		*nitems = unique_elements(type, *values, nulls ? *nulls : NULL,
+ 								  *nitems, nonnulls);
+ 
+ 	return nonnulls;
+ }
+ 
+ /*
+  * Sort an array, and optinally remove duplicated values in it.
+  */
+ static ArrayType *
+ sort_or_unique(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	int			nitems;
+ 	int			lbs = 1;
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	TypeCacheEntry *type;
+ 
+ 	deconstruct_and_sort(array, unique, &values, &nulls, &nitems, fn_extra);
+ 	type = (TypeCacheEntry *) *fn_extra;
+ 
+ 	return construct_md_array(values, nulls, 1, &nitems, &lbs, element_type,
+ 				type->typlen, type->typbyval, type->typalign);
+ }
+ 
+ /*
+  * array_sort :
+  *		Sort an array in ascending order. Nulls are in the last.
+  */
+ Datum
+ array_sort(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_ARRAYTYPE_P(sort_or_unique(
+ 		PG_GETARG_ARRAYTYPE_P(0), false, &fcinfo->flinfo->fn_extra));
+ }
+ 
+ /*
+  * array_to_set :
+  *		Remove duplicated values in an array.
+  */
+ Datum
+ array_to_set(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_ARRAYTYPE_P(sort_or_unique(
+ 		PG_GETARG_ARRAYTYPE_P(0), true, &fcinfo->flinfo->fn_extra));
+ }
+ 
+ /*
+  * array_flatten :
+  *		Flatten a multi-dimensional array to one-dimensional array.
+  */
+ Datum
+ array_flatten(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_ARRAYTYPE_P(
+ 		array_flatten_internal(PG_GETARG_ARRAYTYPE_P(0)));
+ }
+ 
+ /*
+  * array_is_set :
+  *		Return true iff an array has not duplicated values. Note that
+  *		only one null is allowed in a set.
+  */
+ Datum
+ array_is_set(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	bool		result = true;
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	int			nitems;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	FunctionCallInfoData fn;
+ 
+ 	deconstruct_and_sort(array, false, &values, &nulls, &nitems,
+ 		&fcinfo->flinfo->fn_extra);
+ 	type = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ 	InitFunctionCallInfoData(fn, &type->eq_opr_finfo, 2, NULL, NULL);
+ 
+ 	/* compare for each adjacent */
+ 	for (i = 1; i < nitems; i++)
+ 	{
+ 		/* some nulls at end of the array are allowed */
+ 		if (nulls[i])
+ 		{
+ 			result = (i == nitems - 1);	/* only one null is allowd */
+ 			break;
+ 		}
+ 
+ 		fn.arg[0] = values[i - 1];
+ 		fn.arg[1] = values[i];
+ 		fn.argnull[0] = false;
+ 		fn.argnull[1] = false;
+ 		fn.isnull = false;
+ 		if (DatumGetBool(FunctionCallInvoke(&fn)))
+ 		{
+ 			result = false;
+ 			break;
+ 		}
+ 	}
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 
+ 	PG_RETURN_BOOL(result);
+ }
+ 
+ /*
+  * submultiset_of : SUBMULTISET OF
+  *		Return true iff v1 is a subset of v2,
+  */
+ Datum
+ submultiset_of(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1 = NULL;
+ 	ArrayType  *v2 = NULL;
+ 	int			result;			/* true, false, or -1 for null */
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls2,
+ 				n1,
+ 				n2,
+ 				mismatch;
+ 	TypeCacheEntry *type;
+ 	FunctionCallInfoData fn;
+ 
+ 	/* always null when v1 is null */
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		result = -1;
+ 		goto ok;
+ 	}
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	nitems1 = ArrayGetNItems(ARR_NDIM(v1), ARR_DIMS(v1));
+ 
+ 	/* null when v2 is null, but false when v1 is empty */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		result = (nitems1 == 0 ? true : -1);
+ 		goto ok;
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 
+ 	/* true when v1 is empty whether v2 is null or not */
+ 	if (nitems1 == 0)
+ 	{
+ 		result = true;
+ 		goto ok;
+ 	}
+ 
+ 	/* false when v1 has more elements than v2 */
+ 	nitems2 = ArrayGetNItems(ARR_NDIM(v2), ARR_DIMS(v2));
+ 	if (nitems1 > nitems2)
+ 	{
+ 		result = false;
+ 		goto ok;
+ 	}
+ 
+ 	/* null when v1 has some nulls */
+ 	if (ARR_HASNULL(v1))
+ 	{
+ 		result = -1;
+ 		goto ok;
+ 	}
+ 
+ 	/* compare non-null elements */
+ 	deconstruct_and_sort(v1, false,
+ 		&values1, NULL, &nitems1, &fcinfo->flinfo->fn_extra);
+ 	nonnulls2 = deconstruct_and_sort(v2, false,
+ 		&values2, &nulls2, &nitems2, &fcinfo->flinfo->fn_extra);
+ 	type = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ 	InitFunctionCallInfoData(fn, &type->cmp_proc_finfo, 2, NULL, NULL);
+ 
+ 	mismatch = 0;
+ 	for (n1 = n2 = 0;
+ 		 n1 < nitems1 && mismatch <= nitems2 - nonnulls2 &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r;
+ 
+ 		fn.arg[0] = values1[n1];
+ 		fn.arg[1] = values2[n2];
+ 		fn.argnull[0] = false;
+ 		fn.argnull[1] = false;
+ 		fn.isnull = false;
+ 		r = DatumGetInt32(FunctionCallInvoke(&fn));
+ 
+ 		if (r < 0)
+ 			mismatch++;
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	mismatch += nitems1 - n1;
+ 	if (mismatch == 0)
+ 		result = true;
+ 	else if (mismatch > nitems2 - nonnulls2)
+ 		result = false;
+ 	else
+ 		result = -1;	/* v2 has equal or more nulls than mismatches */
+ 
+ ok:
+ 	if (v1 != NULL)
+ 		PG_FREE_IF_COPY(v1, 0);
+ 	if (v2 != NULL)
+ 		PG_FREE_IF_COPY(v2, 1);
+ 
+ 	if (result < 0)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_BOOL(result);
+ }
+ 
+ /*
+  * multiset_union : MULTISET UNION [ DISTINCT | ALL ]
+  *		Concatinate two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ multiset_union(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 	bool		all = PG_GETARG_BOOL(2);
+ 	ArrayType  *result;
+ 	Datum	   *values,
+ 			   *values1,
+ 			   *values2;
+ 	bool	   *nulls,
+ 			   *nulls1,
+ 			   *nulls2;
+ 	int			nitems,
+ 				nitems1,
+ 				nitems2,
+ 				nonnulls;
+ 	Oid			element_type1,
+ 				element_type2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0) && PG_ARGISNULL(1))
+ 		PG_RETURN_NULL();
+ 
+ 	/* fast path for UNION ALL */
+ 	if (all)
+ 		return array_cat_internal(fcinfo, true);
+ 
+ 	/* Concatenating a null array is a no-op, just return the other input */
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 		result = sort_or_unique(v2, true, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v2, 1);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 		result = sort_or_unique(v1, true, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type1 = ARR_ELEMTYPE(v1);
+ 	element_type2 = ARR_ELEMTYPE(v2);
+ 
+ 	check_concatinatable(element_type1, element_type2);
+ 	type = get_type_cache(element_type1, &fcinfo->flinfo->fn_extra);
+ 	deconstruct_array(v1,
+ 					  element_type1,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  &values1, &nulls1, &nitems1);
+ 	deconstruct_array(v2,
+ 					  element_type2,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  &values2, &nulls2, &nitems2);
+ 
+ 	nitems = nitems1 + nitems2;
+ 	values = (Datum *) palloc(sizeof(Datum) * nitems);
+ 	nulls = (bool *) palloc(sizeof(bool) * nitems);
+ 
+ 	memcpy(values, values1, sizeof(Datum) * nitems1);
+ 	memcpy(values + nitems1, values2, sizeof(Datum) * nitems2);
+ 	memcpy(nulls, nulls1, sizeof(bool) * nitems1);
+ 	memcpy(nulls + nitems1, nulls2, sizeof(bool) * nitems2);
+ 
+ 	nonnulls = sort_elements(type, values, nulls, nitems);
+ 	nitems = unique_elements(type, values, nulls, nitems, nonnulls);
+ 	result = construct_md_array(values, nulls, 1, &nitems, &lbs,
+ 								element_type1,
+ 								type->typlen,
+ 								type->typbyval,
+ 								type->typalign);
+ 
+ 	PG_FREE_IF_COPY(v1, 0);
+ 	PG_FREE_IF_COPY(v2, 1);
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * Intersection of two sorted arrays. The first array is modified directly.
+  * Return length of the result.
+  */
+ static int
+ intersect_sorted_arrays(TypeCacheEntry *type,
+ 						Datum *values1, bool *nulls1, int nitems1,
+ 						const Datum *values2, const bool *nulls2, int nitems2)
+ {
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	FunctionCallInfoData fn;
+ 
+ 	InitFunctionCallInfoData(fn, &type->cmp_proc_finfo, 2, NULL, NULL);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r;
+ 
+ 		fn.arg[0] = values1[n1];
+ 		fn.arg[1] = values2[n2];
+ 		fn.argnull[0] = false;
+ 		fn.argnull[1] = false;
+ 		fn.isnull = false;
+ 		r = DatumGetInt32(FunctionCallInvoke(&fn));
+ 
+ 		if (r == 0)
+ 			values1[n++] = values1[n1];
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	/* skip non-nulls */
+ 	for (; n1 < nitems1 && !nulls1[n1]; n1++) {}
+ 	for (; n2 < nitems2 && !nulls2[n2]; n2++) {}
+ 
+ 	/* add nulls */
+ 	for (; n1 < nitems1 && n2 < nitems2; n1++, n2++, n++)
+ 		nulls1[n] = true;
+ 
+ 	return n;
+ }
+ 
+ /*
+  * multiset_intersect : MULTISET INTERSECT [ DISTINCT | ALL ]
+  *		Intersection of two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ multiset_intersect(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	bool		all = PG_GETARG_BOOL(2);
+ 
+ 	ArrayType  *result;
+ 	Oid			element_type = ARR_ELEMTYPE(v1);
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls1,
+ 			   *nulls2;
+ 	int			nitems1,
+ 				nitems2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	deconstruct_and_sort(v1, !all, &values1, &nulls1, &nitems1,
+ 		&fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v2, !all, &values2, &nulls2, &nitems2,
+ 		&fcinfo->flinfo->fn_extra);
+ 	type = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ 
+ 	nitems1 = intersect_sorted_arrays(type,
+ 									  values1, nulls1, nitems1,
+ 									  values2, nulls2, nitems2);
+ 	result = construct_md_array(values1, nulls1, 1, &nitems1, &lbs,
+ 								element_type, type->typlen,
+ 								type->typbyval, type->typalign);
+ 
+ 	PG_FREE_IF_COPY(v1, 0);
+ 	PG_FREE_IF_COPY(v2, 1);
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * multiset_except : MULTISET EXCEPT [ DISTINCT | ALL ]
+  *		Subtraction of two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ multiset_except(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1;
+ 	ArrayType  *v2;
+ 	bool		all;
+ 	ArrayType  *result;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls1,
+ 			   *nulls2;
+ 	int			nitems1,
+ 				nitems2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 	FunctionCallInfoData fn;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	all = PG_GETARG_BOOL(2);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		if (all)
+ 			PG_RETURN_ARRAYTYPE_P(array_flatten_internal(v1));
+ 
+ 		deconstruct_and_sort(v1, false, &values1, &nulls1, &nitems1,
+ 			&fcinfo->flinfo->fn_extra);
+ 		type = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ 
+ 		result = construct_md_array(values1, nulls1, 1, &nitems1, &lbs,
+ 									element_type, type->typlen,
+ 									type->typbyval, type->typalign);
+ 
+ 		PG_FREE_IF_COPY(v1, 0);
+ 
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	check_concatinatable(element_type, ARR_ELEMTYPE(v2));
+ 	deconstruct_and_sort(v1, !all, &values1, &nulls1, &nitems1,
+ 		&fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v2, !all, &values2, &nulls2, &nitems2,
+ 		&fcinfo->flinfo->fn_extra);
+ 	type = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+ 	InitFunctionCallInfoData(fn, &type->cmp_proc_finfo, 2, NULL, NULL);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r;
+ 
+ 		fn.arg[0] = values1[n1];
+ 		fn.arg[1] = values2[n2];
+ 		fn.argnull[0] = false;
+ 		fn.argnull[1] = false;
+ 		fn.isnull = false;
+ 		r = DatumGetInt32(FunctionCallInvoke(&fn));
+ 
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else if (r == 0)
+ 			n1++;
+ 		else
+ 			n2++;
+ 	}
+ 	for (; n1 < nitems1 && !nulls1[n1]; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	if (n1 < nitems1 && nulls1[n1])
+ 	{
+ 		for (; n2 < nitems2 && !nulls2[n2]; n2++) {}
+ 		for (; n1 < nitems1 - (nitems2 - n2); n1++, n++)
+ 			nulls1[n] = true;
+ 	}
+ 
+ 	result = construct_md_array(values1, nulls1, 1, &n, &lbs, element_type,
+ 								type->typlen, type->typbyval, type->typalign);
+ 
+ 	PG_FREE_IF_COPY(v1, 0);
+ 	PG_FREE_IF_COPY(v2, 1);
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * fusion aggregate function :
+  *		Similar to array_agg, but the input values are arrays.
+  */
+ Datum
+ fusion_transfn(PG_FUNCTION_ARGS)
+ {
+ 	MemoryContext		aggcontext;
+ 	ArrayBuildState	   *state;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "fusion_transfn called in non-aggregate context");
+ 	}
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		ArrayType  *v = PG_GETARG_ARRAYTYPE_P(1);
+ 		Oid			elmtype;
+ 		int16		elmlen;
+ 		bool		elmbyval;
+ 		char		elmalign;
+ 		Datum	   *elems;
+ 		bool	   *nulls;
+ 		int			nitems;
+ 		int			i;
+ 
+ 		elmtype = ARR_ELEMTYPE(v);
+ 		get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 		deconstruct_array(v, elmtype, elmlen, elmbyval, elmalign,
+ 						  &elems, &nulls, &nitems);
+ 		for (i = 0; i < nitems; i++)
+ 			state = accumArrayResult(state, elems[i], nulls[i],
+ 									 elmtype, aggcontext);
+ 
+ 		PG_FREE_IF_COPY(v, 1);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ /*
+  * intersection aggregate function :
+  *		Intersection of all input arrays.
+  */
+ typedef struct IntersectState
+ {
+ 	Oid		element_type;
+ 	int		nitems;
+ 	Datum  *values;
+ 	bool   *nulls;
+ } IntersectState;
+ 
+ Datum
+ intersection_transfn(PG_FUNCTION_ARGS)
+ {
+ 	MemoryContext		aggcontext;
+ 	IntersectState	   *state;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "intersection_transfn called in non-aggregate context");
+ 	}
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (IntersectState *) PG_GETARG_POINTER(0);
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		ArrayType  *v = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (IntersectState *) palloc(sizeof(IntersectState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, false,
+ 				&state->values, &state->nulls, &state->nitems,
+ 				&fcinfo->flinfo->fn_extra);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatinatable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, false, &values, &nulls, &nitems,
+ 				&fcinfo->flinfo->fn_extra);
+ 			state->nitems = intersect_sorted_arrays(
+ 				(TypeCacheEntry *) fcinfo->flinfo->fn_extra,
+ 				state->values, state->nulls, state->nitems,
+ 				values, nulls, nitems);
+ 		}
+ 
+ 		PG_FREE_IF_COPY(v, 1);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ intersection_finalfn(PG_FUNCTION_ARGS)
+ {
+ 	IntersectState	   *state;
+ 	ArrayType		   *result;
+ 	int					lbs = 1;
+ 	TypeCacheEntry	   *type;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL : (IntersectState *) PG_GETARG_POINTER(0);
+ 	if (state == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	type = get_type_cache(state->element_type, &fcinfo->flinfo->fn_extra);
+ 	result = construct_md_array(state->values, state->nulls,
+ 								1, &state->nitems, &lbs, state->element_type,
+ 								type->typlen, type->typbyval, type->typalign);
+ 
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * Flatten multi-dimensional array into one-dimensional array.
+  */
+ static ArrayType *
+ array_flatten_internal(ArrayType *array)
+ {
+ 	ArrayType  *result;
+ 	int			ndims = ARR_NDIM(array);
+ 	int32		dataoffset;
+ 	int			ndatabytes,
+ 				nbytes;
+ 	int			nitems;
+ 
+ 	if (ndims <= 1)
+ 		return array;
+ 
+ 	nitems = ArrayGetNItems(ndims, ARR_DIMS(array));
+ 	ndatabytes = ARR_SIZE(array) - ARR_DATA_OFFSET(array);
+ 	if (ARR_HASNULL(array))
+ 	{
+ 		dataoffset = ARR_OVERHEAD_WITHNULLS(1, nitems);
+ 		nbytes = ndatabytes + dataoffset;
+ 	}
+ 	else
+ 	{
+ 		dataoffset = 0;			/* marker for no null bitmap */
+ 		nbytes = ndatabytes + ARR_OVERHEAD_NONULLS(1);
+ 	}
+ 
+ 	result = (ArrayType *) palloc(nbytes);
+ 	SET_VARSIZE(result, nbytes);
+ 	result->ndim = 1;
+ 	result->dataoffset = dataoffset;
+ 	result->elemtype = ARR_ELEMTYPE(array);
+ 	ARR_DIMS(result)[0] = nitems;
+ 	ARR_LBOUND(result)[0] = 1;
+ 	/* data area is arg1 then arg2 */
+ 	memcpy(ARR_DATA_PTR(result), ARR_DATA_PTR(array), ndatabytes);
+ 	/* handle the null bitmap if needed */
+ 	if (ARR_HASNULL(result))
+ 		array_bitmap_copy(ARR_NULLBITMAP(result), 0,
+ 						  ARR_NULLBITMAP(array), 0, nitems);
+ 
+ 	return result;
+ }
+ 
+ static void
+ check_concatinatable(Oid element_type1, Oid element_type2)
+ {
+ 	if (element_type1 != element_type2)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("cannot concatenate incompatible arrays"),
+ 				 errdetail("Arrays with element types %s and %s are not "
+ 						   "compatible for concatenation.",
+ 						   format_type_be(element_type1),
+ 						   format_type_be(element_type2))));
+ }
+ 
+ static void
+ check_comparable(Oid element_type1, Oid element_type2)
+ {
+ 	if (element_type1 != element_type2)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("cannot compare incompatible arrays"),
+ 				 errdetail("Arrays with element types %s and %s are not "
+ 						   "compatible for comparison.",
+ 						   format_type_be(element_type1),
+ 						   format_type_be(element_type2))));
+ }
diff --git a/src/include/catalog/pg_aggregate.h b/src/include/catalog/pg_aggregate.h
index 5ac3492..188a48f 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2901 xmlconcat2	  -					0	
*** 222,227 ****
--- 222,230 ----
  
  /* array */
  DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3210	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3212	fusion_transfn		array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3215	intersection_transfn intersection_finalfn	0	2281	_null_ ));
  
  /* text */
  DATA(insert ( 3538	string_agg_transfn	string_agg_finalfn		0	2281	_null_ ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8e5f502..2a2abe1 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1058,1063 ****
--- 1058,1095 ----
  DESCR("array_agg final function");
  DATA(insert OID = 2335 (  array_agg		   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
  DESCR("concatenate aggregate input into an array");
+ DATA(insert OID = 3200 (  cardinality	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 23 "2277" _null_ _null_ _null_ _null_ array_cardinality _null_ _null_ _null_ ));
+ DESCR("number of elements in array");
+ DATA(insert OID = 3201 (  trim_array	   PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 23" _null_ _null_ _null_ _null_ trim_array _null_ _null_ _null_ ));
+ DESCR("remove elements end of array");
+ DATA(insert OID = 3202 (  array_flatten	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_flatten _null_ _null_ _null_ ));
+ DESCR("flatten a multi-dimensional array into an one-dimensional array");
+ DATA(insert OID = 3203 (  array_sort	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_sort _null_ _null_ _null_ ));
+ DESCR("sort an array in ascending order");
+ DATA(insert OID = 3204 (  set			   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_to_set _null_ _null_ _null_ ));
+ DESCR("remove duplicated values in an array");
+ DATA(insert OID = 3205 (  is_a_set		   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 16 "2277" _null_ _null_ _null_ _null_ array_is_set _null_ _null_ _null_ ));
+ DESCR("no duplicated elements?");
+ DATA(insert OID = 3206 (  submultiset_of   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ submultiset_of _null_ _null_ _null_ ));
+ DESCR("contained as subset?");
+ DATA(insert OID = 3207 (  multiset_union PGNSP PGUID 12 1 0 0 f f f f f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ multiset_union _null_ _null_ _null_ ));
+ DESCR("concatenate two arrays");
+ DATA(insert OID = 3208 (  multiset_intersect PGNSP PGUID 12 1 0 0 f f f t f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ multiset_intersect _null_ _null_ _null_ ));
+ DESCR("intersection of two arrays");
+ DATA(insert OID = 3209 (  multiset_except PGNSP PGUID 12 1 0 0 f f f f f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ multiset_except _null_ _null_ _null_ ));
+ DESCR("exception of two arrays");
+ DATA(insert OID = 3210 (  collect		   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2283" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("concatenate aggregate input into an array");
+ DATA(insert OID = 3211 (  fusion_transfn   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ fusion_transfn _null_ _null_ _null_ ));
+ DESCR("fusion transition function");
+ DATA(insert OID = 3212 (  fusion		   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("concatenate aggregate input into an array");
+ DATA(insert OID = 3213 (  intersection_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ intersection_transfn _null_ _null_ _null_ ));
+ DESCR("intersection transition function");
+ DATA(insert OID = 3214 (  intersection_finalfn PGNSP PGUID 12 1 0 0 f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ intersection_finalfn _null_ _null_ _null_ ));
+ DESCR("intersection final function");
+ DATA(insert OID = 3215 (  intersection	   PGNSP PGUID 12 1 0 0 t f f f f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ aggregate_dummy _null_ _null_ _null_ ));
+ DESCR("intersection of all inputs");
  
  DATA(insert OID = 760 (  smgrin			   PGNSP PGUID 12 1 0 0 f f f t f s 1 0 210 "2275" _null_ _null_ _null_ _null_	smgrin _null_ _null_ _null_ ));
  DESCR("I/O");
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 8f1687f..bd43827 100644
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
*************** extern TypeName *makeTypeNameFromOid(Oid
*** 71,76 ****
--- 71,77 ----
  
  extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype,
  			 List *args, CoercionForm fformat);
+ extern FuncCall *makeFuncCall(List *funcname, List *args, int location);
  
  extern DefElem *makeDefElem(char *name, Node *arg);
  extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2c44cf7..62b5bef 100644
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 26,31 ****
--- 26,32 ----
   */
  
  /* name, value, category */
+ PG_KEYWORD("a", A, UNRESERVED_KEYWORD)
  PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD)
*************** PG_KEYWORD("login", LOGIN_P, UNRESERVED_
*** 232,242 ****
--- 233,245 ----
  PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
  PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
  PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("member", MEMBER, UNRESERVED_KEYWORD)
  PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
  PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
  PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("multiset", MULTISET, UNRESERVED_KEYWORD)
  PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD)
  PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD)
*************** PG_KEYWORD("stdout", STDOUT, UNRESERVED_
*** 356,361 ****
--- 359,365 ----
  PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
  PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
+ PG_KEYWORD("submultiset", SUBMULTISET, UNRESERVED_KEYWORD)
  PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
  PG_KEYWORD("superuser", SUPERUSER_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index dba9c3d..9e5ca6b 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 280,284 ****
--- 280,297 ----
  
  extern Datum array_agg_transfn(PG_FUNCTION_ARGS);
  extern Datum array_agg_finalfn(PG_FUNCTION_ARGS);
+ extern Datum array_cardinality(PG_FUNCTION_ARGS);
+ extern Datum trim_array(PG_FUNCTION_ARGS);
+ extern Datum array_flatten(PG_FUNCTION_ARGS);
+ extern Datum array_sort(PG_FUNCTION_ARGS);
+ extern Datum array_to_set(PG_FUNCTION_ARGS);
+ extern Datum array_is_set(PG_FUNCTION_ARGS);
+ extern Datum submultiset_of(PG_FUNCTION_ARGS);
+ extern Datum multiset_union(PG_FUNCTION_ARGS);
+ extern Datum multiset_intersect(PG_FUNCTION_ARGS);
+ extern Datum multiset_except(PG_FUNCTION_ARGS);
+ extern Datum fusion_transfn(PG_FUNCTION_ARGS);
+ extern Datum intersection_transfn(PG_FUNCTION_ARGS);
+ extern Datum intersection_finalfn(PG_FUNCTION_ARGS);
  
  #endif   /* ARRAY_H */
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index eff5f88..bf7e747 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1286,1288 ****
--- 1286,1468 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- MULTISET support
+ SELECT cardinality(ARRAY[1, 2, 3]);
+  cardinality 
+ -------------
+            3
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 2);
+  trim_array 
+ ------------
+  {1}
+ (1 row)
+ 
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT 3 MEMBER OF ARRAY[1, 2];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+  submultiset_of 
+ ----------------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 1, 2] SUBMULTISET OF ARRAY[1, 2, 2];
+  submultiset_of 
+ ----------------
+  f
+ (1 row)
+ 
+ SELECT ARRAY['A', 'B', 'C'] SUBMULTISET OF ARRAY['A', 'B', 'D'];
+  submultiset_of 
+ ----------------
+  f
+ (1 row)
+ 
+ SELECT ARRAY['A', 'B', 'C'] NOT SUBMULTISET OF ARRAY['A', 'B', 'D'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[]::int[] SUBMULTISET OF NULL::int[];
+  submultiset_of 
+ ----------------
+  t
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF ARRAY[]::int[];
+  submultiset_of 
+ ----------------
+  
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF NULL::int[];
+  submultiset_of 
+ ----------------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];
+  submultiset_of 
+ ----------------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 1, 2, 3];
+  submultiset_of 
+ ----------------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[1, 2, NULL];
+  submultiset_of 
+ ----------------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 4, NULL];
+  submultiset_of 
+ ----------------
+  f
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] IS A SET;
+  is_a_set 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY['A', 'A', 'B'] IS A SET, ARRAY['A', 'A', 'B'] IS NOT A SET;
+  is_a_set | ?column? 
+ ----------+----------
+  f        | t
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] IS A SET, ARRAY[1, NULL, NULL] IS NOT A SET;
+  is_a_set | ?column? 
+ ----------+----------
+  t        | t
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET UNION ALL ARRAY[2, NULL];
+       multiset_union      
+ --------------------------
+  {2,NULL,1,2,NULL,2,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET UNION ARRAY[2, NULL];
+  multiset_union 
+ ----------------
+  {1,2,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET INTERSECT ALL ARRAY[2, NULL];
+  multiset_intersect 
+ --------------------
+  {2,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET INTERSECT ARRAY[2, NULL];
+  multiset_intersect 
+ --------------------
+  {2,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET EXCEPT ALL ARRAY[2, NULL];
+  multiset_except 
+ -----------------
+  {1,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET EXCEPT ARRAY[2, NULL];
+  multiset_except 
+ -----------------
+  {1}
+ (1 row)
+ 
+ SELECT collect(s), fusion(a), intersection(a)
+   FROM (VALUES
+   ('A', ARRAY[1, 2, 3, 2, 2]),
+   ('B', ARRAY[1, 2, 4, 2]),
+   ('C', ARRAY[3, 2, 2, 1])
+ ) AS t(s, a);
+  collect |           fusion            | intersection 
+ ---------+-----------------------------+--------------
+  {A,B,C} | {1,2,3,2,2,1,2,4,2,3,2,2,1} | {1,2,2}
+ (1 row)
+ 
+ SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+       array_sort       
+ -----------------------
+  {1,1,2,3,3,NULL,NULL}
+ (1 row)
+ 
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+      set      
+ --------------
+  {1,2,3,NULL}
+ (1 row)
+ 
+ SELECT array_flatten(ARRAY[ [1, 3], [2, 3] ]);
+  array_flatten 
+ ---------------
+  {1,3,2,3}
+ (1 row)
+ 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index a75b8c4..3b75949 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 426,428 ****
--- 426,465 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- MULTISET support
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]);
+ SELECT trim_array(ARRAY[1, 2, 3], 2);
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+ SELECT 3 MEMBER OF ARRAY[1, 2];
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+ SELECT ARRAY[1, 1, 2] SUBMULTISET OF ARRAY[1, 2, 2];
+ SELECT ARRAY['A', 'B', 'C'] SUBMULTISET OF ARRAY['A', 'B', 'D'];
+ SELECT ARRAY['A', 'B', 'C'] NOT SUBMULTISET OF ARRAY['A', 'B', 'D'];
+ SELECT ARRAY[]::int[] SUBMULTISET OF NULL::int[];
+ SELECT NULL::int[] SUBMULTISET OF ARRAY[]::int[];
+ SELECT NULL::int[] SUBMULTISET OF NULL::int[];
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 1, 2, 3];
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[1, 2, NULL];
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 4, NULL];
+ SELECT ARRAY[1, 2, 3] IS A SET;
+ SELECT ARRAY['A', 'A', 'B'] IS A SET, ARRAY['A', 'A', 'B'] IS NOT A SET;
+ SELECT ARRAY[1, NULL] IS A SET, ARRAY[1, NULL, NULL] IS NOT A SET;
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET UNION ALL ARRAY[2, NULL];
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET UNION ARRAY[2, NULL];
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET INTERSECT ALL ARRAY[2, NULL];
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET INTERSECT ARRAY[2, NULL];
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET EXCEPT ALL ARRAY[2, NULL];
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET EXCEPT ARRAY[2, NULL];
+ SELECT collect(s), fusion(a), intersection(a)
+   FROM (VALUES
+   ('A', ARRAY[1, 2, 3, 2, 2]),
+   ('B', ARRAY[1, 2, 4, 2]),
+   ('C', ARRAY[3, 2, 2, 1])
+ ) AS t(s, a);
+ SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT array_flatten(ARRAY[ [1, 3], [2, 3] ]);