multiset patch review

Started by Pavel Stehuleabout 15 years ago40 messages
#1Pavel Stehule
pavel.stehule@gmail.com

Patching:

patching file doc/src/sgml/func.sgml
Hunk #6 succeeded at 10567 (offset 1 line).
Hunk #7 succeeded at 10621 (offset 1 line).
Hunk #8 succeeded at 10721 (offset 1 line).
Hunk #9 succeeded at 10775 (offset 1 line).
patching file src/backend/nodes/makefuncs.c
patching file src/backend/parser/gram.y
patching file src/backend/utils/adt/array_userfuncs.c
patching file src/include/catalog/pg_aggregate.h
patching file src/include/catalog/pg_proc.h
patching file src/include/nodes/makefuncs.h
patching file src/include/parser/kwlist.h
patching file src/include/utils/array.h
Hunk #1 succeeded at 281 (offset 1 line).
patching file src/test/regress/expected/arrays.out
Hunk #1 succeeded at 1558 (offset 272 lines).
patching file src/test/regress/sql/arrays.sql
Hunk #1 succeeded at 438 (offset 12 lines).

Compilation:

there are no warning related to patch

regress tests failed

*** 1639,1647 ****
..
  SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
         ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
!  submultiset_of | submultiset_of ^M
! ----------------+----------------^M
!                 | f^M
  (1 row)
..
  SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];
--- 1639,1647 ----
..
  SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
         ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
!  submultiset_of | submultiset_of.
! ----------------+----------------
!                 | f
  (1 row)
..
  SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];

There is often used a fragment

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

it can be moved to procedure?

Doc and regress tests are ok.

I see only one issue. There isn't documented, what is a MULTISET?

Regards

Pavel Stehule

#2Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#1)
2 attachment(s)
Re: multiset patch review

Thank you for the review.

On Mon, Jan 10, 2011 at 04:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:

regress tests failed

Fixed.

There is often used a fragment
+ <----><------>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));
it can be moved to procedure?

Agreed. I use FunctionCall2() instead of the fragments.

I see only one issue. There isn't documented, what is a MULTISET?

I added a short description about MULTISET and example of operators
in "Arrays > 8.14.7. Multiset Support" section in the docs.
Is it enough? or what kind of information do you want?

Separate patches for src and doc attached. It includes a few bug fixes
and cleanup. I changed the error code in trim_array() to
ERRCODE_ARRAY_ELEMENT_ERROR according to the spec.

--
Itagaki Takahiro

Attachments:

multiset-doc-20110112.patchapplication/octet-stream; name=multiset-doc-20110112.patchDownload
diff --git a/doc/src/sgml/array.sgml b/doc/src/sgml/array.sgml
index bb4657e..35de925 100644
*** a/doc/src/sgml/array.sgml
--- b/doc/src/sgml/array.sgml
*************** INSERT ... VALUES (E'{"\\\\","\\""}');
*** 706,709 ****
--- 706,817 ----
   </tip>
   </sect2>
  
+  <sect2 id="multisets">
+   <title>Multiset Support</title>
+ 
+   <indexterm>
+    <primary>array</primary>
+    <secondary>multiset</secondary>
+   </indexterm>
+ 
+   <para>
+    Multiset is another collection data type specified in the SQL standard.
+    It is similar to arrays, but the order of elements is irrelevant.
+    <productname>PostgreSQL</productname> doesn't support distinct multiset
+    data type, but has serveral functions and operators based on array types.
+   </para>
+ 
+   <para>
+    <literal>MEMBER OF</> and <literal>SUBMULTISET OF</> operators returns
+    true when the element or subset is contained by the collection.
+    <literal>MEMBER OF</> is exactly same as <literal>= ANY</> operator.
+    On the other hand, <literal>SUBMULTISET OF</> differs from <literal>&lt;@</>
+    because it returns true only if the container have equal or more elements
+    the containded collection.
+ <programlisting>
+ SELECT 2 MEMBER OF ARRAY[1,2], 2 = ANY(ARRAY[1,2]);
+  ?column? | ?column?
+ ----------+----------
+  t        | t
+ (1 row)
+ 
+ SELECT ARRAY[1,1] SUBMULTISET OF ARRAY[1,2],
+        ARRAY[1,1] &lt;@ ARRAY[1,2];
+  submultiset_of | ?column?
+ ----------------+----------
+  f              | t
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>IS A SET</> operator returns true when the collection has
+    no duplicated values. A collection that has two or more NULLs are not
+    considered as a set.
+ <programlisting>
+ SELECT ARRAY[1,2,3] IS A SET,
+        ARRAY[1,1,2] IS A SET,
+        ARRAY[1,NULL,NULL] IS A SET;
+ 
+  is_a_set | is_a_set | is_a_set
+ ----------+----------+----------
+  t        | f        | f
+ (1 row)
+ </programlisting>
+    <function>set</function> function returns a collection of unique elements
+    as like as <literal>DISTINCT</> clause in a query.
+ <programlisting>
+ SELECT set(ARRAY[1,2,NULL,2,NULL,1,2]);
+     set
+ ------------
+  {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>MULTISET EXCEPT</>, <literal>MULTISET INTERSECT</>, and
+    <literal>MULTISET UNION</> operator combine two collections as like as
+    set operations in a query (see <xref linkend="queries-union">).
+    They can have optional <literal>ALL</> or <literal>DISTINCT</> options.
+    If <literal>DISTINCT</> is specified or not specified, they eliminates
+    duplicated elements before the set operations.
+ <programlisting>
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT    ARRAY[2,NULL];
+  multiset_union | multiset_intersect | multiset_except
+ ----------------+--------------------+-----------------
+  {1,2,NULL}     | {2,NULL}           | {1}
+ (1 row)
+ 
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION ALL     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ALL ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT ALL    ARRAY[2,NULL];
+       multiset_union      | multiset_intersect | multiset_except
+ --------------------------+--------------------+-----------------
+  {2,NULL,1,2,NULL,2,NULL} | {2,NULL}           | {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+  <note>
+   <para>
+    Since multisets are actually arrays, some of operators and functions still
+    treats them as arrays. The following example shows two collections are
+    sub-multiset of each other, but not equal with <literal>=</> operator
+    because they are arrays in fact; they have the same set of elements, but
+    differ in the order of elements.
+ <programlisting>
+ SELECT a SUBMULTISET OF b, b SUBMULTISET OF a, a = b
+  FROM (VALUES(ARRAY[1,2], ARRAY[2,1])) t(a, b);
+  submultiset_of | submultiset_of | ?column?
+ ----------------+----------------+----------
+  t              | t              | f
+ (1 row)
+ </programlisting>
+   </para>
+  </note>
+ 
+  </sect2>
  </sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04769f1..aae831c 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT NULLIF(value, '(none)') ...
*** 10196,10201 ****
--- 10196,10311 ----
    </para>
  
    <para>
+    <xref linkend="multiset-operators-table"> shows the multiset operators
+    available for array types. See <xref linkend="multisets"> for more details
+    and limitations.
+   </para>
+ 
+     <table id="multiset-operators-table">
+      <title>Multiset Operators</title>
+      <tgroup cols="4">
+       <thead>
+        <row>
+         <entry>Operator</entry>
+         <entry>Description</entry>
+         <entry>Example</entry>
+         <entry>Result</entry>
+        </row>
+       </thead>
+       <tbody>
+        <row>
+         <entry>
+           <indexterm>
+             <primary>IS A SET</primary>
+           </indexterm>
+           <literal>IS [ NOT ] A SET</literal>
+         </entry>
+         <entry>has only unique elements</entry>
+         <entry><literal>ARRAY[1,2,3] IS A SET</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MEMBER OF</primary>
+           </indexterm>
+           <literal>[ NOT ] MEMBER OF</literal>
+         </entry>
+         <entry>is a member of</entry>
+         <entry><literal>2 MEMBER OF ARRAY[1,2,3]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>SUBMULTISET OF</primary>
+           </indexterm>
+           <literal>[ NOT ] SUBMULTISET OF</literal>
+         </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,2] SUBMULTISET OF ARRAY[3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET EXCEPT</primary>
+           </indexterm>
+           <literal>MULTISET EXCEPT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>subtraction of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET EXCEPT ARRAY[1,3]</literal></entry>
+         <entry><literal>{2}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET INTERSECT</primary>
+           </indexterm>
+           <literal>MULTISET INTERSECT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>intersection of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET INTERSECT ARRAY[1,3]</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+          <indexterm>
+            <primary>MULTISET UNION</primary>
+          </indexterm>
+          <literal>MULTISET UNION [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>union of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET UNION ARRAY[1,3]</literal></entry>
+         <entry><literal>{1,2,3}</literal></entry>
+        </row>
+       </tbody>
+      </tgroup>
+     </table>
+ 
+   <para>
+    In <literal>IS A SET</>, <literal>MEMBER OF</>, <literal>SUBMULTISET OF</>,
+    <literal>MULTISET INTERSECT</>, <literal>MULTISET UNION</>, and
+    <literal>MULTISET EXCEPT</> operators, the order of elements in input array
+    are ignored. They treats the input as a multiset (or bag) rather than an array.
+    Dimension and lower bound of the array don't affect the result at all.
+   </para>
+ 
+   <para>
+    <literal>SUBMULTISET OF</> treats NULLs in input arrays as unknown values.
+    For example, <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL]</> returns
+    NULL. It means we cannot determine whether they matches or not because the
+    NULL in the right hand argument might be 2 or other value. On the other hand,
+    <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL]</> returns false because
+    there are NULL values less than unmatched values.
+   </para>
+ 
+   <para>
     <xref linkend="array-functions-table"> shows the functions
     available for use with array types. See <xref linkend="arrays">
     for more information  and examples of the use of these functions.
*************** SELECT NULLIF(value, '(none)') ...
*** 10226,10240 ****
--- 10336,10362 ----
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
+     <primary>array_sort</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
   <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
+     <primary>cardinality</primary>
+   </indexterm>
+   <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
+     <primary>set</primary>
+   </indexterm>
+   <indexterm>
+     <primary>trim_array</primary>
+   </indexterm>
+   <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10344,10349 ****
--- 10466,10482 ----
         <row>
          <entry>
           <literal>
+           <function>array_sort</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>sort elements in an array in ascending order</entry>
+         <entry><literal>array_sort(ARRAY[3,2,NULL,1])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10379,10384 ****
--- 10512,10550 ----
         <row>
          <entry>
           <literal>
+           <function>cardinality</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>int</type></entry>
+         <entry>returns the number of elements in an array</entry>
+         <entry><literal>cardinality(ARRAY[1,2,3])</literal></entry>
+         <entry><literal>3</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>set</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove duplicated elements in an array</entry>
+         <entry><literal>set(ARRAY[1,3,2,3,NULL,1,NULL])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>trim_array</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove elements at end of an array</entry>
+         <entry><literal>trim_array(ARRAY[1, 2, 3], 2)</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>unnest</function>(<type>anyarray</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10421,10428 ****
     </note>
  
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function> for use with arrays.
     </para>
    </sect1>
  
--- 10587,10601 ----
     </note>
  
     <para>
+     In <function>array_sort</>, <function>set</>, and <function>trim_array</>
+     functions, input arrays are always flattened into one-dimensional arrays.
+     In addition, the lower bounds of the arrays are adjusted to 1.
+    </para>
+ 
+    <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function>, <function>collect</>,
!     <function>fusion</>, and <function>intersection</> for use with arrays.
     </para>
    </sect1>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10468,10474 ****
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any
        </entry>
        <entry>
         array of the argument type
--- 10641,10647 ----
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any non-array
        </entry>
        <entry>
         array of the argument type
*************** SELECT NULLIF(value, '(none)') ...
*** 10568,10573 ****
--- 10741,10762 ----
       <row>
        <entry>
         <indexterm>
+         <primary>collect</primary>
+        </indexterm>
+        <function>collect(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any non-array
+       </entry>
+       <entry>
+        array of the argument type
+       </entry>
+       <entry>an alias for <literal>array_agg</literal></entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>count</primary>
         </indexterm>
         <function>count(*)</function>
*************** SELECT NULLIF(value, '(none)') ...
*** 10606,10611 ****
--- 10795,10832 ----
       <row>
        <entry>
         <indexterm>
+         <primary>fusion</primary>
+        </indexterm>
+        <function>fusion(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>concatenation of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>intersection</primary>
+        </indexterm>
+        <function>intersection(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>intersection of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>max</primary>
         </indexterm>
         <function>max(<replaceable class="parameter">expression</replaceable>)</function>
multiset-src-20110112.patchapplication/octet-stream; name=multiset-src-20110112.patchDownload
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 79da185..df95fee 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 454,459 ****
--- 454,474 ----
  }
  
  /*
+  * 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 660947c..77fb898 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 468,474 ****
   */
  
  /* 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
  
--- 468,474 ----
   */
  
  /* 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
*** 511,517 ****
  	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 NOREPLICATION_P
--- 511,517 ----
  	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 NOREPLICATION_P
*************** static RangeVar *makeRangeVarFromAnyName
*** 535,542 ****
  	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
--- 535,542 ----
  	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
*** 605,610 ****
--- 605,611 ----
  %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; }
*** 9584,9589 ****
--- 9585,9654 ----
  														 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; }
*** 11294,11300 ****
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
--- 11359,11366 ----
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  A
! 			| ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
*************** unreserved_keyword:
*** 11421,11431 ****
--- 11487,11499 ----
  			| MAPPING
  			| MATCH
  			| MAXVALUE
+ 			| MEMBER
  			| MINUTE_P
  			| MINVALUE
  			| MODE
  			| MONTH_P
  			| MOVE
+ 			| MULTISET
  			| NAME_P
  			| NAMES
  			| NEXT
*************** unreserved_keyword:
*** 11513,11518 ****
--- 11581,11587 ----
  			| 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 499d357..e99c0f2 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(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(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  	if (PG_ARGISNULL(1))
  	{
  		result = PG_GETARG_ARRAYTYPE_P(0);
+ 		if (flatten)
+ 			result = array_flatten(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(v2);
  		PG_RETURN_ARRAYTYPE_P(v2);
+ 	}
  
  	if (ndims2 == 0)
+ 	{
+ 		if (flatten)
+ 			v1 = array_flatten(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,1413 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		Return the number of elements in an 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 an array. Multi-dimensional array is
+  *		flattened into one-dimensional array.
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array;
+ 	ArrayType  *result;
+ 	ArrayType  *v;
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	int			nitems;
+ 	int			arrtyplen;
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 	int			lower;
+ 	int			upper;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) must not be negative", ntrimmed)));
+ 
+ 	array = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	nitems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ 	if (ntrimmed > nitems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) is greater than cardinality of collection (%d)", ntrimmed, nitems)));
+ 
+ 	v = array_flatten(array);
+ 	arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0));
+ 	lower = ARR_LBOUND(v)[0];
+ 	upper = ARR_DIMS(v)[0] - ntrimmed;
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	result = array_get_slice(
+ 		v, 1, &upper, &lower, arrtyplen, elmlen, elmbyval, elmalign);
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	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_elements(const void *a, const void *b, void *arg)
+ {
+ 	return DatumGetInt32(FunctionCall2(
+ 		(FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements as an option.
+  */
+ static void
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 			  int nitems, int *nonnulls)
+ {
+ 	int			n,
+ 				i;
+ 
+ 	if (nulls == NULL)
+ 		n = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = n = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[n] = values[i];
+ 				nulls[n] = false;
+ 				n++;
+ 			}
+ 		}
+ 		for (i = n; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	qsort_arg(values, n, sizeof(Datum),
+ 			  compare_elements, &type->cmp_proc_finfo);
+ 
+ 	if (nonnulls)
+ 		*nonnulls = n;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. The values, nulls,
+  * nitems, and nonnulls parameters are modified directly. Note that only
+  * one null value will be kept in the result when there are some nulls.
+  */
+ static void
+ unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls,
+ 				TypeCacheEntry *type)
+ {
+ 	int		i,
+ 			n,
+ 			nvalues = *nonnulls;
+ 	bool	has_nulls = (*nonnulls < *nitems);
+ 
+ 	for (i = n = 1; i < nvalues; i++)
+ 	{
+ 		if (!DatumGetBool(FunctionCall2(
+ 			&type->eq_opr_finfo, values[i - 1], values[i])))
+ 		{
+ 			Assert(!nulls[n]);
+ 			values[n++] = values[i];
+ 		}
+ 	}
+ 	*nonnulls = n;
+ 	if (has_nulls)
+ 		nulls[n++] = true;
+ 	*nitems = n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements and sort them. Returns values,
+  * null, number of all elements and non-null elements as output parameters.
+  * nulls and nonnulls can be NULLs.
+  */
+ static void
+ deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls,
+ 					 int *nitems, int *nonnulls, TypeCacheEntry *type)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	bool	   *tmp_nulls;
+ 
+ 	AssertArg(values != NULL);
+ 	AssertArg(nitems != NULL);
+ 
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, &tmp_nulls, nitems);
+ 	sort_elements(type, *values, tmp_nulls, *nitems, nonnulls);
+ 
+ 	if (nulls)
+ 		*nulls = tmp_nulls;
+ }
+ 
+ /*
+  * A worker for array_sort, array_to_set, and higher-level functions.
+  */
+ static ArrayType *
+ sort_or_unique(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 	Datum		   *values;
+ 	bool		   *nulls;
+ 	int				nitems,
+ 					nonnulls;
+ 	int				lbs = 1;
+ 	Oid				element_type = ARR_ELEMTYPE(array);
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type);
+ 	if (unique)
+ 		unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 
+ 	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)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, false, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_to_set :
+  *		Remove duplicated elements in an array.
+  */
+ Datum
+ array_to_set(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, true, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * 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;
+ 	Datum	   *values;
+ 	int			nitems,
+ 				nonnulls;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	
+ 	type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type);
+ 	if (nitems > nonnulls + 1)
+ 	{
+ 		/* only one null is allowd */
+ 		result = false;
+ 	}
+ 	else
+ 	{
+ 		result = true;
+ 		/* compare for each adjacent */
+ 		for (i = 1; i < nonnulls; i++)
+ 		{
+ 			if (DatumGetBool(FunctionCall2(
+ 				&type->eq_opr_finfo, values[i - 1], values[i])))
+ 			{
+ 				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;
+ 	bool		result = false;
+ 	bool		result_null = false;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2,
+ 				n1,
+ 				n2,
+ 				unmatch;
+ 	TypeCacheEntry *type;
+ 
+ 	/* always null when v1 is null */
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		result_null = true;
+ 		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))
+ 	{
+ 		if (nitems1 == 0)
+ 			result = true;
+ 		else
+ 			result_null = true;
+ 		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;
+ 	}
+ 
+ 	/* compare non-null elements */
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v1, &values1, NULL, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, NULL, &nitems2, &nonnulls2, type);
+ 
+ 	unmatch = 0;
+ 	for (n1 = n2 = 0;
+ 		 n1 < nonnulls1 && unmatch <= nitems2 - nonnulls2 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			unmatch++;
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	unmatch += nitems1 - n1;
+ 	if (unmatch == 0)
+ 		result = true;
+ 	else if (unmatch > nitems2 - nonnulls2)
+ 		result = false;
+ 	else
+ 		result_null = true;	/* v2 has equal or more nulls than unmatches */
+ 
+ ok:
+ 	if (v1 != NULL)
+ 		PG_FREE_IF_COPY(v1, 0);
+ 	if (v2 != NULL)
+ 		PG_FREE_IF_COPY(v2, 1);
+ 
+ 	if (result_null)
+ 		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);
+ 
+ 	sort_elements(type, values, nulls, nitems, &nonnulls);
+ 	unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 	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;
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	all = PG_GETARG_BOOL(2);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		if (all)
+ 			PG_RETURN_ARRAYTYPE_P(array_flatten(v1));
+ 
+ 		result = sort_or_unique(v1, false, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_concatinatable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else
+ 			n2++;
+ 		if (r == 0)
+ 			n1++;
+ 	}
+ 	for (; n1 < nonnulls1; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); 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);
+ 		TypeCacheEntry *type;
+ 
+ 		type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (IntersectState *) palloc(sizeof(IntersectState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, &state->values, &state->nulls,
+ 								 &state->nitems, NULL, type);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatinatable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type);
+ 			state->nitems = intersect_sorted_arrays(type,
+ 								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.
+  * The lower bounds is adjusted to 1.
+  */
+ static ArrayType *
+ array_flatten(ArrayType *array)
+ {
+ 	ArrayType  *result;
+ 	int			ndims = ARR_NDIM(array);
+ 	int32		dataoffset;
+ 	int			ndatabytes,
+ 				nbytes;
+ 	int			nitems;
+ 
+ 	if (ndims < 1 || (ndims == 1 && ARR_LBOUND(array)[0] == 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 26966d2..2adedf3 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 ( 3088	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3090	fusion_transfn		array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3093	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 f8b5d4d..e405e22 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1062,1067 ****
--- 1062,1097 ----
  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 = 3079 (  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 = 3080 (  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 = 3081 (  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 = 3082 (  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 = 3083 (  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 = 3084 (  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 = 3085 (  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 = 3086 (  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 = 3087 (  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 = 3088 (  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 elements into an array");
+ DATA(insert OID = 3089 (  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 = 3090 (  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 arrays into an array");
+ DATA(insert OID = 3091 (  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 = 3092 (  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 = 3093 (  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 7c41312..b5cd584 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 578d3cd..5f59d22 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_
*** 358,363 ****
--- 361,367 ----
  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 7f7e744..cf09606 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 281,285 ****
--- 281,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_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 7b05ce3..955b6ec 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1558,1560 ****
--- 1558,1765 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- MULTISET support
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+  cardinality | cardinality 
+ -------------+-------------
+            3 |           4
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+  trim_array | trim_array | trim_array 
+ ------------+------------+------------
+  {1,2,3}    | {1}        | {1,2,3}
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ ERROR:  number of trimmed elements (-1) must not be negative
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ ERROR:  number of trimmed elements (4) is greater than cardinality of collection (3)
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+  ?column? | ?column? 
+ ----------+----------
+  f        | t
+ (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, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+  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, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+  submultiset_of | submultiset_of 
+ ----------------+----------------
+                 | f
+ (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,2,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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+   array_sort   
+ ---------------
+  {A,B,C,D,E,F}
+ (1 row)
+ 
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+      set      
+ --------------
+  {1,2,3,NULL}
+ (1 row)
+ 
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
+    set   
+ ---------
+  {A,B,C}
+ (1 row)
+ 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 9ea53b1..3c38900 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 438,440 ****
--- 438,485 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- MULTISET support
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+ 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, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+ 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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
#3Peter Eisentraut
peter_e@gmx.net
In reply to: Itagaki Takahiro (#2)
Re: multiset patch review

On ons, 2011-01-12 at 13:52 +0900, Itagaki Takahiro wrote:

I added a short description about MULTISET and example of operators
in "Arrays > 8.14.7. Multiset Support" section in the docs.
Is it enough? or what kind of information do you want?

Separate patches for src and doc attached. It includes a few bug fixes
and cleanup. I changed the error code in trim_array() to
ERRCODE_ARRAY_ELEMENT_ERROR according to the spec.

You may want to read this thread about the cardinality function are you
trying to add:

http://archives.postgresql.org/pgsql-hackers/2009-02/msg01388.php

Also, what happened to the idea of a separate MULTISET type?

#4Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Peter Eisentraut (#3)
Re: multiset patch review

On Wed, Jan 12, 2011 at 15:21, Peter Eisentraut <peter_e@gmx.net> wrote:

You may want to read this thread about the cardinality function are you
trying to add:

http://archives.postgresql.org/pgsql-hackers/2009-02/msg01388.php

Since our archive is split per month, this might be more readable:
http://postgresql.1045698.n5.nabble.com/cardinality-td2003172.html

We've discussed what number should cardinality() returns:
#1. The total number of elements. (It's currently implemented.)
#2. The length of the first dimension.
It's as same as array_length(array, 1) .

I prefer #1 because we have no easy way to retrieve the number.
array_dims() returns similar numbers, but calculate the total
number is a bit complex.

If we will support array of arrays (jugged array), cardinality()
can return the number of elements in the most outer array.
It's similar definition in multi-dimensional arrays in C#,
that has both array of arrays and multi-dimensional arrays.

http://msdn.microsoft.com/library/system.array.length(v=VS.100).aspx

We can compare those SQL functions and C# array methods:
* cardinality(array) <--> array.Length
* array_length(array. dim) <--> array.GetLength(dim)

Also, what happened to the idea of a separate MULTISET type?

I don't have any plans to implement dedicated MULTISET type for now
because almost all functions and operators can work also for arrays.
If we have a true MULTISET data type, we can overload them with
MULTISET arguments.

One exception might be collect() aggregate function because
we might need to change the result type from array to multiset.
collect(anyelement) => anyarray for now
Note that fusion() won't be an issue because we can overload it:
fusion(anyarray) => anyarray and (anymultiset) => anymultiset

--
Itagaki Takahiro

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#2)
Re: multiset patch review

Hello

there is one issue - probably useless checking a type equality in
function check_comparable and check_concatinatable, because when your
function is registrated with arguments (anyarray, anyarray), then is
guaranteed so type of array1 is same as type of array2, and then you
don't need to check.

Regards

Pavel Stehule

2011/1/12 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

Show quoted text

Thank you for the review.

On Mon, Jan 10, 2011 at 04:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:

regress tests failed

Fixed.

There is often used a fragment
+ <----><------>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));
it can be moved to procedure?

Agreed. I use FunctionCall2() instead of the fragments.

I see only one issue. There isn't documented, what is a MULTISET?

I added a short description about MULTISET and example of operators
in "Arrays > 8.14.7. Multiset Support" section in the docs.
Is it enough? or what kind of information do you want?

Separate patches for src and doc attached. It includes a few bug fixes
and cleanup. I changed the error code in trim_array() to
ERRCODE_ARRAY_ELEMENT_ERROR according to the spec.

--
Itagaki Takahiro

#6Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Pavel Stehule (#5)
Re: multiset patch review

On Wed, Jan 12, 2011 at 20:18, Pavel Stehule <pavel.stehule@gmail.com> wrote:

there is one issue - probably useless checking a type equality in
function check_comparable and check_concatinatable, because when your
function is registrated with arguments (anyarray, anyarray), then is
guaranteed so type of array1 is same as type of array2, and then you
don't need to check.

It's true for almost all cases, but we have "anyarray" columns in
pg_statistic.stavaluesN. When we pass them to those array functions,
element types of two anyarrays could be different.
I guess they are protections only for them.

=# SELECT A.stavalues1 SUBMULTISET OF B.stavalues1
FROM pg_statistic A, pg_statistic B
WHERE A.stakind1 = 2 AND B.stakind1 = 2;
ERROR: cannot compare incompatible arrays
DETAIL: Arrays with element types name and oid[] are not compatible
for comparison.

--
Itagaki Takahiro

#7Pavel Stehule
pavel.stehule@gmail.com
In reply to: Itagaki Takahiro (#6)
Re: multiset patch review

2011/1/12 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

On Wed, Jan 12, 2011 at 20:18, Pavel Stehule <pavel.stehule@gmail.com> wrote:

there is one issue - probably useless checking a type equality in
function check_comparable and check_concatinatable, because when your
function is registrated with arguments (anyarray, anyarray), then is
guaranteed so type of array1 is same as type of array2, and then you
don't need to check.

It's true for almost all cases, but we have "anyarray" columns in
pg_statistic.stavaluesN. When we pass them to those array functions,
element types of two anyarrays could be different.
I guess they are protections only for them.

=# SELECT A.stavalues1 SUBMULTISET OF B.stavalues1
  FROM pg_statistic A, pg_statistic B
  WHERE A.stakind1 = 2 AND B.stakind1 = 2;
ERROR:  cannot compare incompatible arrays
DETAIL:  Arrays with element types name and oid[] are not compatible
for comparison.

ook

Pavel

Show quoted text

--
Itagaki Takahiro

#8Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#7)
Re: multiset patch review

2011/1/12 Pavel Stehule <pavel.stehule@gmail.com>:

2011/1/12 Itagaki Takahiro <itagaki.takahiro@gmail.com>:

On Wed, Jan 12, 2011 at 20:18, Pavel Stehule <pavel.stehule@gmail.com> wrote:

there is one issue - probably useless checking a type equality in
function check_comparable and check_concatinatable, because when your
function is registrated with arguments (anyarray, anyarray), then is
guaranteed so type of array1 is same as type of array2, and then you
don't need to check.

It's true for almost all cases, but we have "anyarray" columns in
pg_statistic.stavaluesN. When we pass them to those array functions,
element types of two anyarrays could be different.
I guess they are protections only for them.

=# SELECT A.stavalues1 SUBMULTISET OF B.stavalues1
  FROM pg_statistic A, pg_statistic B
  WHERE A.stakind1 = 2 AND B.stakind1 = 2;
ERROR:  cannot compare incompatible arrays
DETAIL:  Arrays with element types name and oid[] are not compatible
for comparison.

ook

so I think it is ready for commit

Regards

Pavel Stehule

Show quoted text

Pavel

--
Itagaki Takahiro

#9Alvaro Herrera
alvherre@commandprompt.com
In reply to: Itagaki Takahiro (#2)
Re: multiset patch review

Excerpts from Itagaki Takahiro's message of mié ene 12 01:52:12 -0300 2011:

Separate patches for src and doc attached. It includes a few bug fixes
and cleanup. I changed the error code in trim_array() to
ERRCODE_ARRAY_ELEMENT_ERROR according to the spec.

Two small nitpicks:

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

I think the word is either "concatenable" or "concatenatable". Also
please don't break up the string in errdetail() even if it's longer than
80 chars. (The function below this one has this too)

I didn't read the patch in much more detail.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#10Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Alvaro Herrera (#9)
Re: multiset patch review

On Wed, Jan 12, 2011 at 23:29, Alvaro Herrera
<alvherre@commandprompt.com> wrote:

Two small nitpicks:
+ check_concatinatable(Oid element_type1, Oid 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.",

I think the word is either "concatenable" or "concatenatable".  Also
please don't break up the string in errdetail() even if it's longer than
80 chars.  (The function below this one has this too)

OK, I'll fix them,
but the broken up messages come from the existing code.

--
Itagaki Takahiro

#11Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Itagaki Takahiro (#10)
1 attachment(s)
Re: multiset patch review

On Thu, Jan 13, 2011 at 10:40, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

I think the word is either "concatenable" or "concatenatable".  Also
please don't break up the string in errdetail() even if it's longer than
80 chars.  (The function below this one has this too)

OK, I'll fix them,
but the broken up messages come from the existing code.

The attached is a fixed version.

BTW, should we use an "operator" to represent SUBMULTISET OF ?
It is mapped to submultiset_of "function" for now. If GIN and GiST
indexes will support the predicate in the future in addition to <@,
SUBMULTISET OF should be mapped to an operator rather than a function.
We need to choose different operator from <@ and @> for the case
because the semantics are not the same. (ex. <& and &>)

Note that MEMBER OF is represented as "ANY =".

--
Itagaki Takahiro

Attachments:

multiset-20110118.patchapplication/octet-stream; name=multiset-20110118.patchDownload
diff --git a/doc/src/sgml/array.sgml b/doc/src/sgml/array.sgml
index bb4657e..35de925 100644
*** a/doc/src/sgml/array.sgml
--- b/doc/src/sgml/array.sgml
*************** INSERT ... VALUES (E'{"\\\\","\\""}');
*** 706,709 ****
--- 706,817 ----
   </tip>
   </sect2>
  
+  <sect2 id="multisets">
+   <title>Multiset Support</title>
+ 
+   <indexterm>
+    <primary>array</primary>
+    <secondary>multiset</secondary>
+   </indexterm>
+ 
+   <para>
+    Multiset is another collection data type specified in the SQL standard.
+    It is similar to arrays, but the order of elements is irrelevant.
+    <productname>PostgreSQL</productname> doesn't support distinct multiset
+    data type, but has serveral functions and operators based on array types.
+   </para>
+ 
+   <para>
+    <literal>MEMBER OF</> and <literal>SUBMULTISET OF</> operators returns
+    true when the element or subset is contained by the collection.
+    <literal>MEMBER OF</> is exactly same as <literal>= ANY</> operator.
+    On the other hand, <literal>SUBMULTISET OF</> differs from <literal>&lt;@</>
+    because it returns true only if the container have equal or more elements
+    the containded collection.
+ <programlisting>
+ SELECT 2 MEMBER OF ARRAY[1,2], 2 = ANY(ARRAY[1,2]);
+  ?column? | ?column?
+ ----------+----------
+  t        | t
+ (1 row)
+ 
+ SELECT ARRAY[1,1] SUBMULTISET OF ARRAY[1,2],
+        ARRAY[1,1] &lt;@ ARRAY[1,2];
+  submultiset_of | ?column?
+ ----------------+----------
+  f              | t
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>IS A SET</> operator returns true when the collection has
+    no duplicated values. A collection that has two or more NULLs are not
+    considered as a set.
+ <programlisting>
+ SELECT ARRAY[1,2,3] IS A SET,
+        ARRAY[1,1,2] IS A SET,
+        ARRAY[1,NULL,NULL] IS A SET;
+ 
+  is_a_set | is_a_set | is_a_set
+ ----------+----------+----------
+  t        | f        | f
+ (1 row)
+ </programlisting>
+    <function>set</function> function returns a collection of unique elements
+    as like as <literal>DISTINCT</> clause in a query.
+ <programlisting>
+ SELECT set(ARRAY[1,2,NULL,2,NULL,1,2]);
+     set
+ ------------
+  {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>MULTISET EXCEPT</>, <literal>MULTISET INTERSECT</>, and
+    <literal>MULTISET UNION</> operator combine two collections as like as
+    set operations in a query (see <xref linkend="queries-union">).
+    They can have optional <literal>ALL</> or <literal>DISTINCT</> options.
+    If <literal>DISTINCT</> is specified or not specified, they eliminates
+    duplicated elements before the set operations.
+ <programlisting>
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT    ARRAY[2,NULL];
+  multiset_union | multiset_intersect | multiset_except
+ ----------------+--------------------+-----------------
+  {1,2,NULL}     | {2,NULL}           | {1}
+ (1 row)
+ 
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION ALL     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ALL ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT ALL    ARRAY[2,NULL];
+       multiset_union      | multiset_intersect | multiset_except
+ --------------------------+--------------------+-----------------
+  {2,NULL,1,2,NULL,2,NULL} | {2,NULL}           | {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+  <note>
+   <para>
+    Since multisets are actually arrays, some of operators and functions still
+    treats them as arrays. The following example shows two collections are
+    sub-multiset of each other, but not equal with <literal>=</> operator
+    because they are arrays in fact; they have the same set of elements, but
+    differ in the order of elements.
+ <programlisting>
+ SELECT a SUBMULTISET OF b, b SUBMULTISET OF a, a = b
+  FROM (VALUES(ARRAY[1,2], ARRAY[2,1])) t(a, b);
+  submultiset_of | submultiset_of | ?column?
+ ----------------+----------------+----------
+  t              | t              | f
+ (1 row)
+ </programlisting>
+   </para>
+  </note>
+ 
+  </sect2>
  </sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04769f1..aae831c 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT NULLIF(value, '(none)') ...
*** 10196,10201 ****
--- 10196,10311 ----
    </para>
  
    <para>
+    <xref linkend="multiset-operators-table"> shows the multiset operators
+    available for array types. See <xref linkend="multisets"> for more details
+    and limitations.
+   </para>
+ 
+     <table id="multiset-operators-table">
+      <title>Multiset Operators</title>
+      <tgroup cols="4">
+       <thead>
+        <row>
+         <entry>Operator</entry>
+         <entry>Description</entry>
+         <entry>Example</entry>
+         <entry>Result</entry>
+        </row>
+       </thead>
+       <tbody>
+        <row>
+         <entry>
+           <indexterm>
+             <primary>IS A SET</primary>
+           </indexterm>
+           <literal>IS [ NOT ] A SET</literal>
+         </entry>
+         <entry>has only unique elements</entry>
+         <entry><literal>ARRAY[1,2,3] IS A SET</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MEMBER OF</primary>
+           </indexterm>
+           <literal>[ NOT ] MEMBER OF</literal>
+         </entry>
+         <entry>is a member of</entry>
+         <entry><literal>2 MEMBER OF ARRAY[1,2,3]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>SUBMULTISET OF</primary>
+           </indexterm>
+           <literal>[ NOT ] SUBMULTISET OF</literal>
+         </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,2] SUBMULTISET OF ARRAY[3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET EXCEPT</primary>
+           </indexterm>
+           <literal>MULTISET EXCEPT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>subtraction of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET EXCEPT ARRAY[1,3]</literal></entry>
+         <entry><literal>{2}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET INTERSECT</primary>
+           </indexterm>
+           <literal>MULTISET INTERSECT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>intersection of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET INTERSECT ARRAY[1,3]</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+          <indexterm>
+            <primary>MULTISET UNION</primary>
+          </indexterm>
+          <literal>MULTISET UNION [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>union of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET UNION ARRAY[1,3]</literal></entry>
+         <entry><literal>{1,2,3}</literal></entry>
+        </row>
+       </tbody>
+      </tgroup>
+     </table>
+ 
+   <para>
+    In <literal>IS A SET</>, <literal>MEMBER OF</>, <literal>SUBMULTISET OF</>,
+    <literal>MULTISET INTERSECT</>, <literal>MULTISET UNION</>, and
+    <literal>MULTISET EXCEPT</> operators, the order of elements in input array
+    are ignored. They treats the input as a multiset (or bag) rather than an array.
+    Dimension and lower bound of the array don't affect the result at all.
+   </para>
+ 
+   <para>
+    <literal>SUBMULTISET OF</> treats NULLs in input arrays as unknown values.
+    For example, <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL]</> returns
+    NULL. It means we cannot determine whether they matches or not because the
+    NULL in the right hand argument might be 2 or other value. On the other hand,
+    <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL]</> returns false because
+    there are NULL values less than unmatched values.
+   </para>
+ 
+   <para>
     <xref linkend="array-functions-table"> shows the functions
     available for use with array types. See <xref linkend="arrays">
     for more information  and examples of the use of these functions.
*************** SELECT NULLIF(value, '(none)') ...
*** 10226,10240 ****
--- 10336,10362 ----
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
+     <primary>array_sort</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
   <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
+     <primary>cardinality</primary>
+   </indexterm>
+   <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
+     <primary>set</primary>
+   </indexterm>
+   <indexterm>
+     <primary>trim_array</primary>
+   </indexterm>
+   <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10344,10349 ****
--- 10466,10482 ----
         <row>
          <entry>
           <literal>
+           <function>array_sort</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>sort elements in an array in ascending order</entry>
+         <entry><literal>array_sort(ARRAY[3,2,NULL,1])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10379,10384 ****
--- 10512,10550 ----
         <row>
          <entry>
           <literal>
+           <function>cardinality</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>int</type></entry>
+         <entry>returns the number of elements in an array</entry>
+         <entry><literal>cardinality(ARRAY[1,2,3])</literal></entry>
+         <entry><literal>3</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>set</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove duplicated elements in an array</entry>
+         <entry><literal>set(ARRAY[1,3,2,3,NULL,1,NULL])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>trim_array</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove elements at end of an array</entry>
+         <entry><literal>trim_array(ARRAY[1, 2, 3], 2)</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>unnest</function>(<type>anyarray</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10421,10428 ****
     </note>
  
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function> for use with arrays.
     </para>
    </sect1>
  
--- 10587,10601 ----
     </note>
  
     <para>
+     In <function>array_sort</>, <function>set</>, and <function>trim_array</>
+     functions, input arrays are always flattened into one-dimensional arrays.
+     In addition, the lower bounds of the arrays are adjusted to 1.
+    </para>
+ 
+    <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function>, <function>collect</>,
!     <function>fusion</>, and <function>intersection</> for use with arrays.
     </para>
    </sect1>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10468,10474 ****
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any
        </entry>
        <entry>
         array of the argument type
--- 10641,10647 ----
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any non-array
        </entry>
        <entry>
         array of the argument type
*************** SELECT NULLIF(value, '(none)') ...
*** 10568,10573 ****
--- 10741,10762 ----
       <row>
        <entry>
         <indexterm>
+         <primary>collect</primary>
+        </indexterm>
+        <function>collect(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any non-array
+       </entry>
+       <entry>
+        array of the argument type
+       </entry>
+       <entry>an alias for <literal>array_agg</literal></entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>count</primary>
         </indexterm>
         <function>count(*)</function>
*************** SELECT NULLIF(value, '(none)') ...
*** 10606,10611 ****
--- 10795,10832 ----
       <row>
        <entry>
         <indexterm>
+         <primary>fusion</primary>
+        </indexterm>
+        <function>fusion(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>concatenation of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>intersection</primary>
+        </indexterm>
+        <function>intersection(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>intersection of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>max</primary>
         </indexterm>
         <function>max(<replaceable class="parameter">expression</replaceable>)</function>
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 79da185..df95fee 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 454,459 ****
--- 454,474 ----
  }
  
  /*
+  * 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 660947c..88d9369 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 468,474 ****
   */
  
  /* 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
  
--- 468,474 ----
   */
  
  /* 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
*** 511,517 ****
  	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 NOREPLICATION_P
--- 511,517 ----
  	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 NOREPLICATION_P
*************** static RangeVar *makeRangeVarFromAnyName
*** 535,542 ****
  	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
--- 535,542 ----
  	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
*** 605,610 ****
--- 605,611 ----
  %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; }
*** 9584,9589 ****
--- 9585,9645 ----
  														 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 OF a_expr			%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP_ANY,
+ 							"=", $1, $4, @2);
+ 				}
+ 			| a_expr NOT MEMBER OF a_expr		%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP_ANY, "=",
+ 								$1, $5, @2), @2);
+ 				}
+ 			| a_expr SUBMULTISET OF a_expr		%prec SUBMULTISET
+ 				{
+ 					$$ = (Node *) makeFuncCall(
+ 							SystemFuncName("submultiset_of"),
+ 							list_make2($1, $4), @2);
+ 				}
+ 			| a_expr NOT SUBMULTISET OF a_expr	%prec SUBMULTISET
+ 				{
+ 					$$ = (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);
+ 				}
  		;
  
  /*
*************** ColLabel:	IDENT									{ $$ = $1; }
*** 11294,11300 ****
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
--- 11350,11357 ----
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  A
! 			| ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
*************** unreserved_keyword:
*** 11421,11431 ****
--- 11478,11490 ----
  			| MAPPING
  			| MATCH
  			| MAXVALUE
+ 			| MEMBER
  			| MINUTE_P
  			| MINVALUE
  			| MODE
  			| MONTH_P
  			| MOVE
+ 			| MULTISET
  			| NAME_P
  			| NAMES
  			| NEXT
*************** unreserved_keyword:
*** 11513,11518 ****
--- 11572,11578 ----
  			| 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 499d357..89bf52d 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(ArrayType *array);
+ static void check_concatenable(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(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  	if (PG_ARGISNULL(1))
  	{
  		result = PG_GETARG_ARRAYTYPE_P(0);
+ 		if (flatten)
+ 			result = array_flatten(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_concatenable(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(v2);
  		PG_RETURN_ARRAYTYPE_P(v2);
+ 	}
  
  	if (ndims2 == 0)
+ 	{
+ 		if (flatten)
+ 			v1 = array_flatten(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,1411 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		Return the number of elements in an 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 an array. Multi-dimensional array is
+  *		flattened into one-dimensional array.
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array;
+ 	ArrayType  *result;
+ 	ArrayType  *v;
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	int			nitems;
+ 	int			arrtyplen;
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 	int			lower;
+ 	int			upper;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) must not be negative", ntrimmed)));
+ 
+ 	array = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	nitems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ 	if (ntrimmed > nitems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) is greater than cardinality of collection (%d)", ntrimmed, nitems)));
+ 
+ 	v = array_flatten(array);
+ 	arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0));
+ 	lower = ARR_LBOUND(v)[0];
+ 	upper = ARR_DIMS(v)[0] - ntrimmed;
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	result = array_get_slice(
+ 		v, 1, &upper, &lower, arrtyplen, elmlen, elmbyval, elmalign);
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	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_elements(const void *a, const void *b, void *arg)
+ {
+ 	return DatumGetInt32(FunctionCall2(
+ 		(FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements as an option.
+  */
+ static void
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 			  int nitems, int *nonnulls)
+ {
+ 	int			n,
+ 				i;
+ 
+ 	if (nulls == NULL)
+ 		n = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = n = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[n] = values[i];
+ 				nulls[n] = false;
+ 				n++;
+ 			}
+ 		}
+ 		for (i = n; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	qsort_arg(values, n, sizeof(Datum),
+ 			  compare_elements, &type->cmp_proc_finfo);
+ 
+ 	if (nonnulls)
+ 		*nonnulls = n;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. The values, nulls,
+  * nitems, and nonnulls parameters are modified directly. Note that only
+  * one null value will be kept in the result when there are some nulls.
+  */
+ static void
+ unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls,
+ 				TypeCacheEntry *type)
+ {
+ 	int		i,
+ 			n,
+ 			nvalues = *nonnulls;
+ 	bool	has_nulls = (*nonnulls < *nitems);
+ 
+ 	for (i = n = 1; i < nvalues; i++)
+ 	{
+ 		if (!DatumGetBool(FunctionCall2(
+ 			&type->eq_opr_finfo, values[i - 1], values[i])))
+ 		{
+ 			Assert(!nulls[n]);
+ 			values[n++] = values[i];
+ 		}
+ 	}
+ 	*nonnulls = n;
+ 	if (has_nulls)
+ 		nulls[n++] = true;
+ 	*nitems = n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements and sort them. Returns values,
+  * null, number of all elements and non-null elements as output parameters.
+  * nulls and nonnulls can be NULLs.
+  */
+ static void
+ deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls,
+ 					 int *nitems, int *nonnulls, TypeCacheEntry *type)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	bool	   *tmp_nulls;
+ 
+ 	AssertArg(values != NULL);
+ 	AssertArg(nitems != NULL);
+ 
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, &tmp_nulls, nitems);
+ 	sort_elements(type, *values, tmp_nulls, *nitems, nonnulls);
+ 
+ 	if (nulls)
+ 		*nulls = tmp_nulls;
+ }
+ 
+ /*
+  * A worker for array_sort, array_to_set, and higher-level functions.
+  */
+ static ArrayType *
+ sort_or_unique(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 	Datum		   *values;
+ 	bool		   *nulls;
+ 	int				nitems,
+ 					nonnulls;
+ 	int				lbs = 1;
+ 	Oid				element_type = ARR_ELEMTYPE(array);
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type);
+ 	if (unique)
+ 		unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 
+ 	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)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, false, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_to_set :
+  *		Remove duplicated elements in an array.
+  */
+ Datum
+ array_to_set(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, true, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * 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;
+ 	Datum	   *values;
+ 	int			nitems,
+ 				nonnulls;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	
+ 	type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type);
+ 	if (nitems > nonnulls + 1)
+ 	{
+ 		/* only one null is allowd */
+ 		result = false;
+ 	}
+ 	else
+ 	{
+ 		result = true;
+ 		/* compare for each adjacent */
+ 		for (i = 1; i < nonnulls; i++)
+ 		{
+ 			if (DatumGetBool(FunctionCall2(
+ 				&type->eq_opr_finfo, values[i - 1], values[i])))
+ 			{
+ 				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;
+ 	bool		result = false;
+ 	bool		result_null = false;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2,
+ 				n1,
+ 				n2,
+ 				unmatch;
+ 	TypeCacheEntry *type;
+ 
+ 	/* always null when v1 is null */
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		result_null = true;
+ 		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))
+ 	{
+ 		if (nitems1 == 0)
+ 			result = true;
+ 		else
+ 			result_null = true;
+ 		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;
+ 	}
+ 
+ 	/* compare non-null elements */
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v1, &values1, NULL, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, NULL, &nitems2, &nonnulls2, type);
+ 
+ 	unmatch = 0;
+ 	for (n1 = n2 = 0;
+ 		 n1 < nonnulls1 && unmatch <= nitems2 - nonnulls2 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			unmatch++;
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	unmatch += nitems1 - n1;
+ 	if (unmatch == 0)
+ 		result = true;
+ 	else if (unmatch > nitems2 - nonnulls2)
+ 		result = false;
+ 	else
+ 		result_null = true;	/* v2 has equal or more nulls than unmatches */
+ 
+ ok:
+ 	if (v1 != NULL)
+ 		PG_FREE_IF_COPY(v1, 0);
+ 	if (v2 != NULL)
+ 		PG_FREE_IF_COPY(v2, 1);
+ 
+ 	if (result_null)
+ 		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_concatenable(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);
+ 
+ 	sort_elements(type, values, nulls, nitems, &nonnulls);
+ 	unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 	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;
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	all = PG_GETARG_BOOL(2);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		if (all)
+ 			PG_RETURN_ARRAYTYPE_P(array_flatten(v1));
+ 
+ 		result = sort_or_unique(v1, false, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_concatenable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else
+ 			n2++;
+ 		if (r == 0)
+ 			n1++;
+ 	}
+ 	for (; n1 < nonnulls1; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); 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);
+ 		TypeCacheEntry *type;
+ 
+ 		type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (IntersectState *) palloc(sizeof(IntersectState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, &state->values, &state->nulls,
+ 								 &state->nitems, NULL, type);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatenable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type);
+ 			state->nitems = intersect_sorted_arrays(type,
+ 								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.
+  * The lower bounds is adjusted to 1.
+  */
+ static ArrayType *
+ array_flatten(ArrayType *array)
+ {
+ 	ArrayType  *result;
+ 	int			ndims = ARR_NDIM(array);
+ 	int32		dataoffset;
+ 	int			ndatabytes,
+ 				nbytes;
+ 	int			nitems;
+ 
+ 	if (ndims < 1 || (ndims == 1 && ARR_LBOUND(array)[0] == 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_concatenable(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 26966d2..2adedf3 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 ( 3088	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3090	fusion_transfn		array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3093	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 f8b5d4d..e405e22 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1062,1067 ****
--- 1062,1097 ----
  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 = 3079 (  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 = 3080 (  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 = 3081 (  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 = 3082 (  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 = 3083 (  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 = 3084 (  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 = 3085 (  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 = 3086 (  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 = 3087 (  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 = 3088 (  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 elements into an array");
+ DATA(insert OID = 3089 (  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 = 3090 (  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 arrays into an array");
+ DATA(insert OID = 3091 (  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 = 3092 (  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 = 3093 (  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 7c41312..b5cd584 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 578d3cd..5f59d22 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_
*** 358,363 ****
--- 361,367 ----
  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 7f7e744..cf09606 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 281,285 ****
--- 281,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_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 7b05ce3..955b6ec 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1558,1560 ****
--- 1558,1765 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- MULTISET support
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+  cardinality | cardinality 
+ -------------+-------------
+            3 |           4
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+  trim_array | trim_array | trim_array 
+ ------------+------------+------------
+  {1,2,3}    | {1}        | {1,2,3}
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ ERROR:  number of trimmed elements (-1) must not be negative
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ ERROR:  number of trimmed elements (4) is greater than cardinality of collection (3)
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+  ?column? | ?column? 
+ ----------+----------
+  f        | t
+ (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, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+  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, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+  submultiset_of | submultiset_of 
+ ----------------+----------------
+                 | f
+ (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,2,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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+   array_sort   
+ ---------------
+  {A,B,C,D,E,F}
+ (1 row)
+ 
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+      set      
+ --------------
+  {1,2,3,NULL}
+ (1 row)
+ 
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
+    set   
+ ---------
+  {A,B,C}
+ (1 row)
+ 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 9ea53b1..3c38900 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 438,440 ****
--- 438,485 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- MULTISET support
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+ 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, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+ 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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
#12Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Itagaki Takahiro (#11)
1 attachment(s)
Re: multiset patch review

On Tue, Jan 18, 2011 at 17:39, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

BTW, should we use an "operator" to represent SUBMULTISET OF ?

I did it in the attached patch. Also, I fixed a bug of NULL checks
in SUBMULTISET OF operator.

Now SUBMULTISET OF is an alias to the new <& operator. We also have
&> operator as the commutator. They are different from <@ and @>
operators because they considers the number of elements.

For example:
=# SELECT ARRAY[1,1] <@ ARRAY[1], ARRAY[1,1] <& ARRAY[1];
?column? | ?column?
----------+----------
t | f
(1 row)

GIN still doesn't support <& and &> operators because of NULL handling.
In the spec, all values including NULLs should be returned for an empty
array key (i.e, "WHERE ARRAY[] SUBMULTISET OF array_col" returns everything),
but the current GIN implementation won't return NULL values for non-NULL keys.
Since it requires changes in GIN, I'd like to postpone gin support to the next
development cycle for 9.2.

--
Itagaki Takahiro

Attachments:

multiset-20110124.patchapplication/octet-stream; name=multiset-20110124.patchDownload
diff --git a/doc/src/sgml/array.sgml b/doc/src/sgml/array.sgml
index bb4657e..487a219 100644
*** a/doc/src/sgml/array.sgml
--- b/doc/src/sgml/array.sgml
*************** INSERT ... VALUES (E'{"\\\\","\\""}');
*** 706,709 ****
--- 706,819 ----
   </tip>
   </sect2>
  
+  <sect2 id="multisets">
+   <title>Multiset Support</title>
+ 
+   <indexterm>
+    <primary>array</primary>
+    <secondary>multiset</secondary>
+   </indexterm>
+ 
+   <para>
+    Multiset is another collection data type specified in the SQL standard.
+    It is similar to arrays, but the order of elements is irrelevant.
+    <productname>PostgreSQL</productname> doesn't support distinct multiset
+    data type, but has serveral functions and operators based on array types.
+   </para>
+ 
+   <para>
+    <literal>MEMBER OF</> and <literal>SUBMULTISET OF</> operators returns
+    true when the element or subset is contained by the collection.
+    <literal>&lt;&amp;</> operator is an alias <literal>SUBMULTISET OF</> operator,
+    and <literal>&amp;&gt;</> operator is the commutator of them.
+    <literal>MEMBER OF</> is exactly same as <literal>= ANY</> operator.
+    On the other hand, <literal>SUBMULTISET OF</> differs from <literal>&lt;@</>
+    (contained operator) because it returns true only if the container have equal
+    or more elements the containded collection.
+ <programlisting>
+ SELECT 2 MEMBER OF ARRAY[1,2], 2 = ANY(ARRAY[1,2]);
+  ?column? | ?column?
+ ----------+----------
+  t        | t
+ (1 row)
+ 
+ SELECT ARRAY[1,1] SUBMULTISET OF ARRAY[1,2],
+        ARRAY[1,1] &lt;@ ARRAY[1,2];
+  ?column? | ?column?
+ ----------+----------
+  f        | t
+   (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>IS A SET</> operator returns true when the collection has
+    no duplicated values. A collection that has two or more NULLs are not
+    considered as a set.
+ <programlisting>
+ SELECT ARRAY[1,2,3] IS A SET,
+        ARRAY[1,1,2] IS A SET,
+        ARRAY[1,NULL,NULL] IS A SET;
+ 
+  is_a_set | is_a_set | is_a_set
+ ----------+----------+----------
+  t        | f        | f
+ (1 row)
+ </programlisting>
+    <function>set</function> function returns a collection of unique elements
+    as like as <literal>DISTINCT</> clause in a query.
+ <programlisting>
+ SELECT set(ARRAY[1,2,NULL,2,NULL,1,2]);
+     set
+ ------------
+  {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>MULTISET EXCEPT</>, <literal>MULTISET INTERSECT</>, and
+    <literal>MULTISET UNION</> operator combine two collections as like as
+    set operations in a query (see <xref linkend="queries-union">).
+    They can have optional <literal>ALL</> or <literal>DISTINCT</> options.
+    If <literal>DISTINCT</> is specified or not specified, they eliminates
+    duplicated elements before the set operations.
+ <programlisting>
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT    ARRAY[2,NULL];
+  multiset_union | multiset_intersect | multiset_except
+ ----------------+--------------------+-----------------
+  {1,2,NULL}     | {2,NULL}           | {1}
+ (1 row)
+ 
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION ALL     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ALL ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT ALL    ARRAY[2,NULL];
+       multiset_union      | multiset_intersect | multiset_except
+ --------------------------+--------------------+-----------------
+  {2,NULL,1,2,NULL,2,NULL} | {2,NULL}           | {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+  <note>
+   <para>
+    Since multisets are actually arrays, some of operators and functions still
+    treats them as arrays. The following example shows two collections are
+    sub-multiset of each other, but not equal with <literal>=</> operator
+    because they are arrays in fact; they have the same set of elements, but
+    differ in the order of elements.
+ <programlisting>
+ SELECT a SUBMULTISET OF b, b SUBMULTISET OF a, a = b
+  FROM (VALUES(ARRAY[1,2], ARRAY[2,1])) t(a, b);
+  ?column? | ?column? | ?column?
+ ----------+----------+----------
+  t        | t        | f
+ (1 row)
+ </programlisting>
+   </para>
+  </note>
+ 
+  </sect2>
  </sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 04769f1..31166ae 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT NULLIF(value, '(none)') ...
*** 10196,10201 ****
--- 10196,10325 ----
    </para>
  
    <para>
+    <xref linkend="multiset-operators-table"> shows the multiset operators
+    available for array types. See <xref linkend="multisets"> for more details
+    and limitations.
+   </para>
+ 
+     <table id="multiset-operators-table">
+      <title>Multiset Operators</title>
+      <tgroup cols="4">
+       <thead>
+        <row>
+         <entry>Operator</entry>
+         <entry>Description</entry>
+         <entry>Example</entry>
+         <entry>Result</entry>
+        </row>
+       </thead>
+       <tbody>
+        <row>
+         <entry>
+           <indexterm>
+             <primary>IS A SET</primary>
+           </indexterm>
+           <literal>IS [ NOT ] A SET</literal>
+         </entry>
+         <entry>has only unique elements</entry>
+         <entry><literal>ARRAY[1,2,3] IS A SET</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MEMBER OF</primary>
+           </indexterm>
+           <literal>[ NOT ] MEMBER OF</literal>
+         </entry>
+         <entry>is a member of</entry>
+         <entry><literal>2 MEMBER OF ARRAY[1,2,3]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>SUBMULTISET OF</primary>
+           </indexterm>
+           <literal>[ NOT ] SUBMULTISET OF</literal>
+         </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,2] SUBMULTISET OF ARRAY[3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry> <literal>&amp;&gt;</literal> </entry>
+         <entry>is a superset of</entry>
+         <entry><literal>ARRAY[1,3,2,1] &amp;&gt; ARRAY[1,1,2]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry> <literal>&lt;&amp;</literal> </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,1,2] &lt;&amp; ARRAY[1,3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET EXCEPT</primary>
+           </indexterm>
+           <literal>MULTISET EXCEPT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>subtraction of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET EXCEPT ARRAY[1,3]</literal></entry>
+         <entry><literal>{2}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET INTERSECT</primary>
+           </indexterm>
+           <literal>MULTISET INTERSECT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>intersection of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET INTERSECT ARRAY[1,3]</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+          <indexterm>
+            <primary>MULTISET UNION</primary>
+          </indexterm>
+          <literal>MULTISET UNION [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>union of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET UNION ARRAY[1,3]</literal></entry>
+         <entry><literal>{1,2,3}</literal></entry>
+        </row>
+       </tbody>
+      </tgroup>
+     </table>
+ 
+   <para>
+    In <literal>IS A SET</>, <literal>MEMBER OF</>, <literal>SUBMULTISET OF</>,
+    <literal>MULTISET INTERSECT</>, <literal>MULTISET UNION</>, and
+    <literal>MULTISET EXCEPT</> operators, the order of elements in input array
+    are ignored. They treats the input as a multiset (or bag) rather than an array.
+    Dimension and lower bound of the array don't affect the result at all.
+   </para>
+ 
+   <para>
+    <literal>SUBMULTISET OF</> treats NULLs in input arrays as unknown values.
+    For example, <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL]</> returns
+    NULL. It means we cannot determine whether they matches or not because the
+    NULL in the right hand argument might be 2 or other value. On the other hand,
+    <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL]</> returns false because
+    there are NULL values less than unmatched values.
+   </para>
+ 
+   <para>
     <xref linkend="array-functions-table"> shows the functions
     available for use with array types. See <xref linkend="arrays">
     for more information  and examples of the use of these functions.
*************** SELECT NULLIF(value, '(none)') ...
*** 10226,10240 ****
--- 10350,10376 ----
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
+     <primary>array_sort</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
   <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
+     <primary>cardinality</primary>
+   </indexterm>
+   <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
+     <primary>set</primary>
+   </indexterm>
+   <indexterm>
+     <primary>trim_array</primary>
+   </indexterm>
+   <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10344,10349 ****
--- 10480,10496 ----
         <row>
          <entry>
           <literal>
+           <function>array_sort</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>sort elements in an array in ascending order</entry>
+         <entry><literal>array_sort(ARRAY[3,2,NULL,1])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10379,10384 ****
--- 10526,10564 ----
         <row>
          <entry>
           <literal>
+           <function>cardinality</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>int</type></entry>
+         <entry>returns the number of elements in an array</entry>
+         <entry><literal>cardinality(ARRAY[1,2,3])</literal></entry>
+         <entry><literal>3</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>set</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove duplicated elements in an array</entry>
+         <entry><literal>set(ARRAY[1,3,2,3,NULL,1,NULL])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>trim_array</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove elements at end of an array</entry>
+         <entry><literal>trim_array(ARRAY[1, 2, 3], 2)</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>unnest</function>(<type>anyarray</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10421,10428 ****
     </note>
  
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function> for use with arrays.
     </para>
    </sect1>
  
--- 10601,10615 ----
     </note>
  
     <para>
+     In <function>array_sort</>, <function>set</>, and <function>trim_array</>
+     functions, input arrays are always flattened into one-dimensional arrays.
+     In addition, the lower bounds of the arrays are adjusted to 1.
+    </para>
+ 
+    <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function>, <function>collect</>,
!     <function>fusion</>, and <function>intersection</> for use with arrays.
     </para>
    </sect1>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10468,10474 ****
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any
        </entry>
        <entry>
         array of the argument type
--- 10655,10661 ----
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any non-array
        </entry>
        <entry>
         array of the argument type
*************** SELECT NULLIF(value, '(none)') ...
*** 10568,10573 ****
--- 10755,10776 ----
       <row>
        <entry>
         <indexterm>
+         <primary>collect</primary>
+        </indexterm>
+        <function>collect(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any non-array
+       </entry>
+       <entry>
+        array of the argument type
+       </entry>
+       <entry>an alias for <literal>array_agg</literal></entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>count</primary>
         </indexterm>
         <function>count(*)</function>
*************** SELECT NULLIF(value, '(none)') ...
*** 10606,10611 ****
--- 10809,10846 ----
       <row>
        <entry>
         <indexterm>
+         <primary>fusion</primary>
+        </indexterm>
+        <function>fusion(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>concatenation of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>intersection</primary>
+        </indexterm>
+        <function>intersection(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>intersection of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>max</primary>
         </indexterm>
         <function>max(<replaceable class="parameter">expression</replaceable>)</function>
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 79da185..df95fee 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 454,459 ****
--- 454,474 ----
  }
  
  /*
+  * 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 660947c..1148ad7 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 468,474 ****
   */
  
  /* 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
  
--- 468,474 ----
   */
  
  /* 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
*** 511,517 ****
  	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 NOREPLICATION_P
--- 511,517 ----
  	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 NOREPLICATION_P
*************** static RangeVar *makeRangeVarFromAnyName
*** 535,542 ****
  	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
--- 535,542 ----
  	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
*** 605,610 ****
--- 605,611 ----
  %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; }
*** 9584,9589 ****
--- 9585,9643 ----
  														 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 OF a_expr			%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP_ANY,
+ 							"=", $1, $4, @2);
+ 				}
+ 			| a_expr NOT MEMBER OF a_expr		%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP_ANY, "=",
+ 								$1, $5, @2), @2);
+ 				}
+ 			| a_expr SUBMULTISET OF a_expr		%prec SUBMULTISET
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP,
+ 							"<&", $1, $4, @2);
+ 				}
+ 			| a_expr NOT SUBMULTISET OF a_expr	%prec SUBMULTISET
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP,
+ 								"<&", $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);
+ 				}
  		;
  
  /*
*************** ColLabel:	IDENT									{ $$ = $1; }
*** 11294,11300 ****
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
--- 11348,11355 ----
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  A
! 			| ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
*************** unreserved_keyword:
*** 11421,11431 ****
--- 11476,11488 ----
  			| MAPPING
  			| MATCH
  			| MAXVALUE
+ 			| MEMBER
  			| MINUTE_P
  			| MINVALUE
  			| MODE
  			| MONTH_P
  			| MOVE
+ 			| MULTISET
  			| NAME_P
  			| NAMES
  			| NEXT
*************** unreserved_keyword:
*** 11513,11518 ****
--- 11570,11576 ----
  			| 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 499d357..412f6d8 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(ArrayType *array);
+ static void check_concatenable(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(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  	if (PG_ARGISNULL(1))
  	{
  		result = PG_GETARG_ARRAYTYPE_P(0);
+ 		if (flatten)
+ 			result = array_flatten(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_concatenable(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(v2);
  		PG_RETURN_ARRAYTYPE_P(v2);
+ 	}
  
  	if (ndims2 == 0)
+ 	{
+ 		if (flatten)
+ 			v1 = array_flatten(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,1427 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		Return the number of elements in an 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 an array. Multi-dimensional array is
+  *		flattened into one-dimensional array.
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array;
+ 	ArrayType  *result;
+ 	ArrayType  *v;
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	int			nitems;
+ 	int			arrtyplen;
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 	int			lower;
+ 	int			upper;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) must not be negative", ntrimmed)));
+ 
+ 	array = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	nitems = ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array));
+ 	if (ntrimmed > nitems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) is greater than cardinality of collection (%d)", ntrimmed, nitems)));
+ 
+ 	v = array_flatten(array);
+ 	arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0));
+ 	lower = ARR_LBOUND(v)[0];
+ 	upper = ARR_DIMS(v)[0] - ntrimmed;
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	result = array_get_slice(
+ 		v, 1, &upper, &lower, arrtyplen, elmlen, elmbyval, elmalign);
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	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_elements(const void *a, const void *b, void *arg)
+ {
+ 	return DatumGetInt32(FunctionCall2(
+ 		(FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements as an option.
+  */
+ static void
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 			  int nitems, int *nonnulls)
+ {
+ 	int			n,
+ 				i;
+ 
+ 	if (nulls == NULL)
+ 		n = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = n = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[n] = values[i];
+ 				nulls[n] = false;
+ 				n++;
+ 			}
+ 		}
+ 		for (i = n; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	qsort_arg(values, n, sizeof(Datum),
+ 			  compare_elements, &type->cmp_proc_finfo);
+ 
+ 	if (nonnulls)
+ 		*nonnulls = n;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. The values, nulls,
+  * nitems, and nonnulls parameters are modified directly. Note that only
+  * one null value will be kept in the result when there are some nulls.
+  */
+ static void
+ unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls,
+ 				TypeCacheEntry *type)
+ {
+ 	int		i,
+ 			n,
+ 			nvalues = *nonnulls;
+ 	bool	has_nulls = (*nonnulls < *nitems);
+ 
+ 	for (i = n = 1; i < nvalues; i++)
+ 	{
+ 		if (!DatumGetBool(FunctionCall2(
+ 			&type->eq_opr_finfo, values[i - 1], values[i])))
+ 		{
+ 			Assert(!nulls[n]);
+ 			values[n++] = values[i];
+ 		}
+ 	}
+ 	*nonnulls = n;
+ 	if (has_nulls)
+ 		nulls[n++] = true;
+ 	*nitems = n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements and sort them. Returns values,
+  * null, number of all elements and non-null elements as output parameters.
+  * nulls and nonnulls can be NULLs.
+  */
+ static void
+ deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls,
+ 					 int *nitems, int *nonnulls, TypeCacheEntry *type)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	bool	   *tmp_nulls;
+ 
+ 	AssertArg(values != NULL);
+ 	AssertArg(nitems != NULL);
+ 
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, &tmp_nulls, nitems);
+ 	sort_elements(type, *values, tmp_nulls, *nitems, nonnulls);
+ 
+ 	if (nulls)
+ 		*nulls = tmp_nulls;
+ }
+ 
+ /*
+  * A worker for array_sort, array_to_set, and higher-level functions.
+  */
+ static ArrayType *
+ sort_or_unique(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 	Datum		   *values;
+ 	bool		   *nulls;
+ 	int				nitems,
+ 					nonnulls;
+ 	int				lbs = 1;
+ 	Oid				element_type = ARR_ELEMTYPE(array);
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type);
+ 	if (unique)
+ 		unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 
+ 	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)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, false, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_to_set :
+  *		Remove duplicated elements in an array.
+  */
+ Datum
+ array_to_set(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, true, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * 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;
+ 	Datum	   *values;
+ 	int			nitems,
+ 				nonnulls;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	
+ 	type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type);
+ 	if (nitems > nonnulls + 1)
+ 	{
+ 		/* only one null is allowd */
+ 		result = false;
+ 	}
+ 	else
+ 	{
+ 		result = true;
+ 		/* compare for each adjacent */
+ 		for (i = 1; i < nonnulls; i++)
+ 		{
+ 			if (DatumGetBool(FunctionCall2(
+ 				&type->eq_opr_finfo, values[i - 1], values[i])))
+ 			{
+ 				result = false;
+ 				break;
+ 			}
+ 		}
+ 	}
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_BOOL(result);
+ }
+ 
+ static Datum
+ submultiset_internal(PG_FUNCTION_ARGS, int arg_contained, int arg_contains)
+ {
+ 	ArrayType  *v1 = NULL;
+ 	ArrayType  *v2 = NULL;
+ 	bool		result = false;
+ 	bool		result_null = false;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2,
+ 				n1,
+ 				n2,
+ 				unmatch;
+ 	TypeCacheEntry *type;
+ 
+ 	/* always null when v1 is null */
+ 	if (PG_ARGISNULL(arg_contained))
+ 	{
+ 		result_null = true;
+ 		goto ok;
+ 	}
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(arg_contained);
+ 	nitems1 = ArrayGetNItems(ARR_NDIM(v1), ARR_DIMS(v1));
+ 
+ 	/* null when v2 is null, but false when v1 is empty */
+ 	if (PG_ARGISNULL(arg_contains))
+ 	{
+ 		if (nitems1 == 0)
+ 			result = true;
+ 		else
+ 			result_null = true;
+ 		goto ok;
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(arg_contains);
+ 	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;
+ 	}
+ 
+ 	/* compare non-null elements */
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v1, &values1, NULL, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, NULL, &nitems2, &nonnulls2, type);
+ 
+ 	unmatch = 0;
+ 	for (n1 = n2 = 0;
+ 		 n1 < nonnulls1 && unmatch <= nitems2 - nonnulls2 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			unmatch++;
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	unmatch += nonnulls1 - n1;
+ 	if (unmatch == 0 && nitems1 - nonnulls1 <= 0)
+ 		result = true;
+ 	else if (unmatch > nitems2 - nonnulls2)
+ 		result = false;
+ 	else
+ 		result_null = true;	/* v2 has equal or more nulls than unmatches */
+ 
+ ok:
+ 	if (v1 != NULL)
+ 		PG_FREE_IF_COPY(v1, arg_contained);
+ 	if (v2 != NULL)
+ 		PG_FREE_IF_COPY(v2, arg_contains);
+ 
+ 	if (result_null)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_BOOL(result);
+ }
+ 
+ /*
+  * submultiset_of : SUBMULTISET OF
+  *		Return true iff v1 is a subset of v2,
+  */
+ Datum
+ submultiset_of(PG_FUNCTION_ARGS)
+ {
+ 	return submultiset_internal(fcinfo, 0, 1);
+ }
+ 
+ /*
+  * supermultiset_of
+  *		Commutator for SUBMULTISET OF
+  */
+ Datum
+ supermultiset_of(PG_FUNCTION_ARGS)
+ {
+ 	return submultiset_internal(fcinfo, 1, 0);
+ }
+ 
+ /*
+  * 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_concatenable(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);
+ 
+ 	sort_elements(type, values, nulls, nitems, &nonnulls);
+ 	unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 	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;
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	all = PG_GETARG_BOOL(2);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		if (all)
+ 			PG_RETURN_ARRAYTYPE_P(array_flatten(v1));
+ 
+ 		result = sort_or_unique(v1, false, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_concatenable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else
+ 			n2++;
+ 		if (r == 0)
+ 			n1++;
+ 	}
+ 	for (; n1 < nonnulls1; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); 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);
+ 		TypeCacheEntry *type;
+ 
+ 		type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (IntersectState *) palloc(sizeof(IntersectState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, &state->values, &state->nulls,
+ 								 &state->nitems, NULL, type);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatenable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type);
+ 			state->nitems = intersect_sorted_arrays(type,
+ 								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.
+  * The lower bounds is adjusted to 1.
+  */
+ static ArrayType *
+ array_flatten(ArrayType *array)
+ {
+ 	ArrayType  *result;
+ 	int			ndims = ARR_NDIM(array);
+ 	int32		dataoffset;
+ 	int			ndatabytes,
+ 				nbytes;
+ 	int			nitems;
+ 
+ 	if (ndims < 1 || (ndims == 1 && ARR_LBOUND(array)[0] == 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_concatenable(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 26966d2..b1b0171 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 ( 3089	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3091	fusion_transfn		array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3094	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_operator.h b/src/include/catalog/pg_operator.h
index b69b60a..e76320c 100644
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DATA(insert OID = 2590 (  "|&>"    PGNSP
*** 867,872 ****
--- 867,874 ----
  DATA(insert OID = 2750 (  "&&"	   PGNSP PGUID b f f 2277 2277	16 2750  0 arrayoverlap areasel areajoinsel ));
  DATA(insert OID = 2751 (  "@>"	   PGNSP PGUID b f f 2277 2277	16 2752  0 arraycontains contsel contjoinsel ));
  DATA(insert OID = 2752 (  "<@"	   PGNSP PGUID b f f 2277 2277	16 2751  0 arraycontained contsel contjoinsel ));
+ DATA(insert OID = 3095 (  "&>"	   PGNSP PGUID b f f 2277 2277	16 3096  0 supermultiset_of contsel contjoinsel ));
+ DATA(insert OID = 3096 (  "<&"	   PGNSP PGUID b f f 2277 2277	16 3095  0 submultiset_of contsel contjoinsel ));
  
  /* capturing operators to preserve pre-8.3 behavior of text concatenation */
  DATA(insert OID = 2779 (  "||"	   PGNSP PGUID b f f 25 2776	25	 0 0 textanycat - - ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8b5d4d..adbebac 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1062,1067 ****
--- 1062,1099 ----
  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 = 3079 (  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 = 3080 (  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 = 3081 (  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 = 3082 (  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 = 3083 (  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 = 3084 (  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 = 3085 (  supermultiset_of PGNSP PGUID 12 1 0 0 f f f f f i 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ supermultiset_of _null_ _null_ _null_ ));
+ DESCR("contains as subset?");
+ DATA(insert OID = 3086 (  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 = 3087 (  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 = 3088 (  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 = 3089 (  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 elements into an array");
+ DATA(insert OID = 3090 (  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 = 3091 (  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 arrays into an array");
+ DATA(insert OID = 3092 (  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 = 3093 (  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 = 3094 (  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 7c41312..b5cd584 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 578d3cd..5f59d22 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_
*** 358,363 ****
--- 361,367 ----
  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 7f7e744..99b52e0 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 281,285 ****
--- 281,298 ----
  
  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_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 supermultiset_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 7b05ce3..4276535 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1558,1560 ****
--- 1558,1772 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- MULTISET support
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+  cardinality | cardinality 
+ -------------+-------------
+            3 |           4
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+  trim_array | trim_array | trim_array 
+ ------------+------------+------------
+  {1,2,3}    | {1}        | {1,2,3}
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ ERROR:  number of trimmed elements (-1) must not be negative
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ ERROR:  number of trimmed elements (4) is greater than cardinality of collection (3)
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+  ?column? | ?column? 
+ ----------+----------
+  f        | t
+ (1 row)
+ 
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 1, 2] SUBMULTISET OF ARRAY[1, 2, 2];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ SELECT ARRAY['A', 'B', 'C'] SUBMULTISET OF ARRAY['A', 'B', 'D'];
+  ?column? 
+ ----------
+  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[];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF ARRAY[]::int[];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF NULL::int[];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, 2],
+        ARRAY[1, NULL] SUBMULTISET OF ARRAY[2, 3];
+  ?column? | ?column? 
+ ----------+----------
+           | f
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+  ?column? | ?column? 
+ ----------+----------
+           | f
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 1, 2, 3];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[1, 2, NULL];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 4, NULL];
+  ?column? 
+ ----------
+  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,2,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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+   array_sort   
+ ---------------
+  {A,B,C,D,E,F}
+ (1 row)
+ 
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+      set      
+ --------------
+  {1,2,3,NULL}
+ (1 row)
+ 
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
+    set   
+ ---------
+  {A,B,C}
+ (1 row)
+ 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 9ea53b1..331a86a 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 438,440 ****
--- 438,487 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- MULTISET support
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+ 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, 2],
+        ARRAY[1, NULL] SUBMULTISET OF ARRAY[2, 3];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+ 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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
#13Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#12)
Re: multiset patch review

On Mon, Jan 24, 2011 at 2:45 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

[ latest patch ]

I notice that this is adding keywords and syntax support for what is
basically a PostgreSQL extension (since we certainly can't possibly be
following the SQL standards given that we're not implementing a new
datatype. Is that really a good idea?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#14Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#13)
Re: multiset patch review

On Mon, Jan 24, 2011 at 20:49, Robert Haas <robertmhaas@gmail.com> wrote:

I notice that this is adding keywords and syntax support for what is
basically a PostgreSQL extension (since we certainly can't possibly be
following the SQL standards given that we're not implementing a new
datatype.  Is that really a good idea?

As I wrote here,
http://archives.postgresql.org/pgsql-hackers/2011-01/msg00829.php
I think we can follow the SQL standard incrementally because we
have function overloads.

One exception is the result type of collect() aggregate function.
It returns an array for now, but will return a multiset when we
support true multiset data type.

--
Itagaki Takahiro

#15Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#14)
Re: multiset patch review

On Mon, Jan 24, 2011 at 7:27 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Mon, Jan 24, 2011 at 20:49, Robert Haas <robertmhaas@gmail.com> wrote:

I notice that this is adding keywords and syntax support for what is
basically a PostgreSQL extension (since we certainly can't possibly be
following the SQL standards given that we're not implementing a new
datatype.  Is that really a good idea?

As I wrote here,
http://archives.postgresql.org/pgsql-hackers/2011-01/msg00829.php
I think we can follow the SQL standard incrementally because we
have function overloads.

One exception is the result type of collect() aggregate function.
It returns an array for now, but will return a multiset when we
support true multiset data type.

So, the plan is to add this now with non-standard semantics and then
change the semantics later if and when we implement what the standard
requires? That's not something we usually do, and I don't see why
it's a better idea in this case than it is in general. It's OK to
have non-standard behavior with non-standard syntax, but I think
non-standard behavior with standard syntax is something we want to try
hard to avoid.

I'm in favor of rejecting this patch in its entirety. The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#15)
Re: multiset patch review

2011/1/30 Robert Haas <robertmhaas@gmail.com>:

On Mon, Jan 24, 2011 at 7:27 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Mon, Jan 24, 2011 at 20:49, Robert Haas <robertmhaas@gmail.com> wrote:

I notice that this is adding keywords and syntax support for what is
basically a PostgreSQL extension (since we certainly can't possibly be
following the SQL standards given that we're not implementing a new
datatype.  Is that really a good idea?

As I wrote here,
http://archives.postgresql.org/pgsql-hackers/2011-01/msg00829.php
I think we can follow the SQL standard incrementally because we
have function overloads.

One exception is the result type of collect() aggregate function.
It returns an array for now, but will return a multiset when we
support true multiset data type.

So, the plan is to add this now with non-standard semantics and then
change the semantics later if and when we implement what the standard
requires?  That's not something we usually do, and I don't see why
it's a better idea in this case than it is in general.  It's OK to
have non-standard behavior with non-standard syntax, but I think
non-standard behavior with standard syntax is something we want to try
hard to avoid.

I'm in favor of rejecting this patch in its entirety.  The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

Hello

It must not be a significant problem with compatibility, because
implemented operators and functions are implemented for arrays.
Functions from this patch are very useful - there are lot of
implementations in SQL language, and this implementation means a
significant speed. I can't to believe so there can be situation, when
pg will has a true support of collection and operations with arrays
will not offer similar functionality. I propose a remove collect()
aggregate, but all others functions and operators can stay.

And if this isn't acceptable for Robert, then I like implementation
of these functions without parser's changes as minimum. Function like
array_sort, array_distinct and some variants array_union are really
missing (should be in core).

Regards
Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#15)
Re: multiset patch review

Robert Haas <robertmhaas@gmail.com> writes:

So, the plan is to add this now with non-standard semantics and then
change the semantics later if and when we implement what the standard
requires? That's not something we usually do, and I don't see why
it's a better idea in this case than it is in general. It's OK to
have non-standard behavior with non-standard syntax, but I think
non-standard behavior with standard syntax is something we want to try
hard to avoid.

I'm in favor of rejecting this patch in its entirety. The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

+1 ... if we're going to provide nonstandard behavior, it should be with
a different syntax. Also, with a contrib module we could keep on
providing the nonstandard behavior for people who still need it, even
after implementing the standard properly.

regards, tom lane

#18Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#17)
Re: multiset patch review

On Sun, Jan 30, 2011 at 12:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

So, the plan is to add this now with non-standard semantics and then
change the semantics later if and when we implement what the standard
requires?  That's not something we usually do, and I don't see why
it's a better idea in this case than it is in general.  It's OK to
have non-standard behavior with non-standard syntax, but I think
non-standard behavior with standard syntax is something we want to try
hard to avoid.

I'm in favor of rejecting this patch in its entirety.  The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

+1 ... if we're going to provide nonstandard behavior, it should be with
a different syntax.  Also, with a contrib module we could keep on
providing the nonstandard behavior for people who still need it, even
after implementing the standard properly.

Good point.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#19Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#18)
Re: multiset patch review

On Mon, Jan 31, 2011 at 02:34, Robert Haas <robertmhaas@gmail.com> wrote:

I'm in favor of rejecting this patch in its entirety.  The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

+1 ... if we're going to provide nonstandard behavior, it should be with
a different syntax.  Also, with a contrib module we could keep on
providing the nonstandard behavior for people who still need it, even
after implementing the standard properly.

Good point.

I agree for collect() function, that is the only function we cannot
provide compatibility when we have MULTISET. But others are still
reasonable because they won't provide nonstandard behavior.

The SQL standard seems to have abstract COLLECTION data type as a
super class of ARRAY and MULTISET. So, it's reasonable that
functions and operators that accept MULTISETs also accept ARRAYs.
For example, we will have cardinality(ARRAY) even if we have
cardinality(MULTISET). Also, trim_array() is in the SQL standard.

I can remove some parts in the patch, especially for parser changes,
but others should be still in the core.

--
Itagaki Takahiro

#20Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#19)
Re: multiset patch review

On Sun, Jan 30, 2011 at 1:46 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Mon, Jan 31, 2011 at 02:34, Robert Haas <robertmhaas@gmail.com> wrote:

I'm in favor of rejecting this patch in its entirety.  The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

+1 ... if we're going to provide nonstandard behavior, it should be with
a different syntax.  Also, with a contrib module we could keep on
providing the nonstandard behavior for people who still need it, even
after implementing the standard properly.

Good point.

I agree for collect() function, that is the only function we cannot
provide compatibility when we have MULTISET. But others are still
reasonable because they won't provide nonstandard behavior.

The SQL standard seems to have abstract COLLECTION data type as a
super class of ARRAY and MULTISET. So, it's reasonable that
functions and operators that accept MULTISETs also accept ARRAYs.
For example, we will have cardinality(ARRAY) even if we have
cardinality(MULTISET). Also, trim_array() is in the SQL standard.

I can remove some parts in the patch, especially for parser changes,
but others should be still in the core.

Well, do you want to revise this and submit a stripped-down version?
I'm not averse to adding things that are required by the standard and
won't cause backward compatibility problems later.

The documentation for trim_array() in the current patch version is
pretty terrible. The documentation describes it

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#21Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#20)
Re: multiset patch review

On Sun, Jan 30, 2011 at 2:11 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Sun, Jan 30, 2011 at 1:46 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Mon, Jan 31, 2011 at 02:34, Robert Haas <robertmhaas@gmail.com> wrote:

I'm in favor of rejecting this patch in its entirety.  The
functionality looks useful, but once you remove the syntax support, it
could just as easily be distributed as a contrib module rather than in
core.

+1 ... if we're going to provide nonstandard behavior, it should be with
a different syntax.  Also, with a contrib module we could keep on
providing the nonstandard behavior for people who still need it, even
after implementing the standard properly.

Good point.

I agree for collect() function, that is the only function we cannot
provide compatibility when we have MULTISET. But others are still
reasonable because they won't provide nonstandard behavior.

The SQL standard seems to have abstract COLLECTION data type as a
super class of ARRAY and MULTISET. So, it's reasonable that
functions and operators that accept MULTISETs also accept ARRAYs.
For example, we will have cardinality(ARRAY) even if we have
cardinality(MULTISET). Also, trim_array() is in the SQL standard.

I can remove some parts in the patch, especially for parser changes,
but others should be still in the core.

Well, do you want to revise this and submit a stripped-down version?
I'm not averse to adding things that are required by the standard and
won't cause backward compatibility problems later.

The documentation for trim_array() in the current patch version is
pretty terrible.  The documentation describes it

Argh, sorry.

The documentation describe it as having the prototype
trim_array(anyarray), but it's called in the example as
trim(integer[], integer) - two arguments vs. one. Also the docs don't
say how it decides how many elements to remove, or what happens to a
multi-dimensional array.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#22Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#21)
Re: multiset patch review

On Mon, Jan 31, 2011 at 04:12, Robert Haas <robertmhaas@gmail.com> wrote:

Well, do you want to revise this and submit a stripped-down version?
I'm not averse to adding things that are required by the standard and
won't cause backward compatibility problems later.

Sure. I'll remove collect() function. I can also remove syntax support,
but it requires rework for documentation (for, IS A SET operator to
is_a_set function), so I'd like to wait for the final consensus a bit more.

The documentation for trim_array() in the current patch version is
pretty terrible.  The documentation describe it as having the prototype
trim_array(anyarray), but it's called in the example as
trim(integer[], integer) - two arguments vs. one.

Oops, it's just my mistake. trim(anyarray, integer) is correct.

 Also the docs don't
say how it decides how many elements to remove, or what happens to a
multi-dimensional array.

I wrote the description below in the patch:
+     In <function>array_sort</>, <function>set</>, and <function>trim_array</>
+     functions, input arrays are always flattened into one-dimensional arrays.
+     In addition, the lower bounds of the arrays are adjusted to 1.

I'm not sure what is the consistent behavior for MD arrays. For example,
array_concat() is very strict, but <@ and && operators don't care about
the dimensions. I interpreted the second argument for trim_array() as
a number of "elements", but of course we can redefine it as a number of
"rows" for MD arrays.

--
Itagaki Takahiro

#23Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Itagaki Takahiro (#22)
1 attachment(s)
Re: multiset patch review

On Mon, Jan 31, 2011 at 04:34, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Mon, Jan 31, 2011 at 04:12, Robert Haas <robertmhaas@gmail.com> wrote:

Well, do you want to revise this and submit a stripped-down version?
I'm not averse to adding things that are required by the standard and
won't cause backward compatibility problems later.

I removed collect() aggregate function because the result type is MULTISET
in the spec. I keep all of other functions and operators because they won't
break anything in the standard. When we will have true MULTISET data type,
we can overload those functions and operators for MULTISET and ARRAY.

The documentation for trim_array() in the current patch version is
pretty terrible.  The documentation describe it as having the prototype
trim_array(anyarray), but it's called in the example as
trim(integer[], integer) - two arguments vs. one.

Oops, it's just my mistake. trim(anyarray, integer) is correct.

Fixed and add an example for a MD array.

 Also the docs don't
say how it decides how many elements to remove, or what happens to a
multi-dimensional array.

Now it removes supplied number of slices at the end of array.
trim_array( ARRAY[[1,2],[3,4]], 1) ==> ARRAY[[1,2]]
Also, it keep lower-bounds of the input array, that is an advantage
over slice syntax. Slice syntax always reset lower-bounds to 1.

--
Itagaki Takahiro

Attachments:

multiset-20110131.patchapplication/octet-stream; name=multiset-20110131.patchDownload
diff --git a/doc/src/sgml/array.sgml b/doc/src/sgml/array.sgml
index bb4657e..487a219 100644
*** a/doc/src/sgml/array.sgml
--- b/doc/src/sgml/array.sgml
*************** INSERT ... VALUES (E'{"\\\\","\\""}');
*** 706,709 ****
--- 706,819 ----
   </tip>
   </sect2>
  
+  <sect2 id="multisets">
+   <title>Multiset Support</title>
+ 
+   <indexterm>
+    <primary>array</primary>
+    <secondary>multiset</secondary>
+   </indexterm>
+ 
+   <para>
+    Multiset is another collection data type specified in the SQL standard.
+    It is similar to arrays, but the order of elements is irrelevant.
+    <productname>PostgreSQL</productname> doesn't support distinct multiset
+    data type, but has serveral functions and operators based on array types.
+   </para>
+ 
+   <para>
+    <literal>MEMBER OF</> and <literal>SUBMULTISET OF</> operators returns
+    true when the element or subset is contained by the collection.
+    <literal>&lt;&amp;</> operator is an alias <literal>SUBMULTISET OF</> operator,
+    and <literal>&amp;&gt;</> operator is the commutator of them.
+    <literal>MEMBER OF</> is exactly same as <literal>= ANY</> operator.
+    On the other hand, <literal>SUBMULTISET OF</> differs from <literal>&lt;@</>
+    (contained operator) because it returns true only if the container have equal
+    or more elements the containded collection.
+ <programlisting>
+ SELECT 2 MEMBER OF ARRAY[1,2], 2 = ANY(ARRAY[1,2]);
+  ?column? | ?column?
+ ----------+----------
+  t        | t
+ (1 row)
+ 
+ SELECT ARRAY[1,1] SUBMULTISET OF ARRAY[1,2],
+        ARRAY[1,1] &lt;@ ARRAY[1,2];
+  ?column? | ?column?
+ ----------+----------
+  f        | t
+   (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>IS A SET</> operator returns true when the collection has
+    no duplicated values. A collection that has two or more NULLs are not
+    considered as a set.
+ <programlisting>
+ SELECT ARRAY[1,2,3] IS A SET,
+        ARRAY[1,1,2] IS A SET,
+        ARRAY[1,NULL,NULL] IS A SET;
+ 
+  is_a_set | is_a_set | is_a_set
+ ----------+----------+----------
+  t        | f        | f
+ (1 row)
+ </programlisting>
+    <function>set</function> function returns a collection of unique elements
+    as like as <literal>DISTINCT</> clause in a query.
+ <programlisting>
+ SELECT set(ARRAY[1,2,NULL,2,NULL,1,2]);
+     set
+ ------------
+  {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+   <para>
+    <literal>MULTISET EXCEPT</>, <literal>MULTISET INTERSECT</>, and
+    <literal>MULTISET UNION</> operator combine two collections as like as
+    set operations in a query (see <xref linkend="queries-union">).
+    They can have optional <literal>ALL</> or <literal>DISTINCT</> options.
+    If <literal>DISTINCT</> is specified or not specified, they eliminates
+    duplicated elements before the set operations.
+ <programlisting>
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT    ARRAY[2,NULL];
+  multiset_union | multiset_intersect | multiset_except
+ ----------------+--------------------+-----------------
+  {1,2,NULL}     | {2,NULL}           | {1}
+ (1 row)
+ 
+ SELECT ARRAY[2,NULL,1,2,NULL] MULTISET UNION ALL     ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET INTERSECT ALL ARRAY[2,NULL],
+        ARRAY[2,NULL,1,2,NULL] MULTISET EXCEPT ALL    ARRAY[2,NULL];
+       multiset_union      | multiset_intersect | multiset_except
+ --------------------------+--------------------+-----------------
+  {2,NULL,1,2,NULL,2,NULL} | {2,NULL}           | {1,2,NULL}
+ (1 row)
+ </programlisting>
+   </para>
+ 
+  <note>
+   <para>
+    Since multisets are actually arrays, some of operators and functions still
+    treats them as arrays. The following example shows two collections are
+    sub-multiset of each other, but not equal with <literal>=</> operator
+    because they are arrays in fact; they have the same set of elements, but
+    differ in the order of elements.
+ <programlisting>
+ SELECT a SUBMULTISET OF b, b SUBMULTISET OF a, a = b
+  FROM (VALUES(ARRAY[1,2], ARRAY[2,1])) t(a, b);
+  ?column? | ?column? | ?column?
+ ----------+----------+----------
+  t        | t        | f
+ (1 row)
+ </programlisting>
+   </para>
+  </note>
+ 
+  </sect2>
  </sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ed2039c..b2eebf6 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT NULLIF(value, '(none)') ...
*** 10196,10201 ****
--- 10196,10325 ----
    </para>
  
    <para>
+    <xref linkend="multiset-operators-table"> shows the multiset operators
+    available for array types. See <xref linkend="multisets"> for more details
+    and limitations.
+   </para>
+ 
+     <table id="multiset-operators-table">
+      <title>Multiset Operators</title>
+      <tgroup cols="4">
+       <thead>
+        <row>
+         <entry>Operator</entry>
+         <entry>Description</entry>
+         <entry>Example</entry>
+         <entry>Result</entry>
+        </row>
+       </thead>
+       <tbody>
+        <row>
+         <entry>
+           <indexterm>
+             <primary>IS A SET</primary>
+           </indexterm>
+           <literal>IS [ NOT ] A SET</literal>
+         </entry>
+         <entry>has only unique elements</entry>
+         <entry><literal>ARRAY[1,2,3] IS A SET</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MEMBER OF</primary>
+           </indexterm>
+           <literal>[ NOT ] MEMBER OF</literal>
+         </entry>
+         <entry>is a member of</entry>
+         <entry><literal>2 MEMBER OF ARRAY[1,2,3]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>SUBMULTISET OF</primary>
+           </indexterm>
+           <literal>[ NOT ] SUBMULTISET OF</literal>
+         </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,2] SUBMULTISET OF ARRAY[3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry> <literal>&amp;&gt;</literal> </entry>
+         <entry>is a superset of</entry>
+         <entry><literal>ARRAY[1,3,2,1] &amp;&gt; ARRAY[1,1,2]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry> <literal>&lt;&amp;</literal> </entry>
+         <entry>is a subset of</entry>
+         <entry><literal>ARRAY[1,1,2] &lt;&amp; ARRAY[1,3,2,1]</literal></entry>
+         <entry><literal>t</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET EXCEPT</primary>
+           </indexterm>
+           <literal>MULTISET EXCEPT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>subtraction of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET EXCEPT ARRAY[1,3]</literal></entry>
+         <entry><literal>{2}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+           <indexterm>
+             <primary>MULTISET INTERSECT</primary>
+           </indexterm>
+           <literal>MULTISET INTERSECT [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>intersection of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET INTERSECT ARRAY[1,3]</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+ 
+        <row>
+         <entry>
+          <indexterm>
+            <primary>MULTISET UNION</primary>
+          </indexterm>
+          <literal>MULTISET UNION [ ALL | DISTINCT ]</literal>
+         </entry>
+         <entry>union of</entry>
+         <entry><literal>ARRAY[1,1,2] MULTISET UNION ARRAY[1,3]</literal></entry>
+         <entry><literal>{1,2,3}</literal></entry>
+        </row>
+       </tbody>
+      </tgroup>
+     </table>
+ 
+   <para>
+    In <literal>IS A SET</>, <literal>MEMBER OF</>, <literal>SUBMULTISET OF</>,
+    <literal>MULTISET INTERSECT</>, <literal>MULTISET UNION</>, and
+    <literal>MULTISET EXCEPT</> operators, the order of elements in input array
+    are ignored. They treats the input as a multiset (or bag) rather than an array.
+    Dimension and lower bound of the array don't affect the result at all.
+   </para>
+ 
+   <para>
+    <literal>SUBMULTISET OF</> treats NULLs in input arrays as unknown values.
+    For example, <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL]</> returns
+    NULL. It means we cannot determine whether they matches or not because the
+    NULL in the right hand argument might be 2 or other value. On the other hand,
+    <literal>ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL]</> returns false because
+    there are NULL values less than unmatched values.
+   </para>
+ 
+   <para>
     <xref linkend="array-functions-table"> shows the functions
     available for use with array types. See <xref linkend="arrays">
     for more information  and examples of the use of these functions.
*************** SELECT NULLIF(value, '(none)') ...
*** 10226,10240 ****
--- 10350,10376 ----
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
+     <primary>array_sort</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
   <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
+     <primary>cardinality</primary>
+   </indexterm>
+   <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
+     <primary>set</primary>
+   </indexterm>
+   <indexterm>
+     <primary>trim_array</primary>
+   </indexterm>
+   <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10344,10349 ****
--- 10480,10496 ----
         <row>
          <entry>
           <literal>
+           <function>array_sort</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>sort elements in an array in ascending order</entry>
+         <entry><literal>array_sort(ARRAY[3,2,NULL,1])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10379,10384 ****
--- 10526,10564 ----
         <row>
          <entry>
           <literal>
+           <function>cardinality</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>int</type></entry>
+         <entry>returns the number of elements in an array</entry>
+         <entry><literal>cardinality(ARRAY[1,2,3])</literal></entry>
+         <entry><literal>3</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>set</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove duplicated elements in an array</entry>
+         <entry><literal>set(ARRAY[1,3,2,3,NULL,1,NULL])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>trim_array</function>(<type>anyarray</type>, <type>integer</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove supplied number of elements at end of an array</entry>
+         <entry><literal>trim_array(ARRAY[1,2,3], 2), trim_array(ARRAY[[1,2],[3,4]], 1)</literal></entry>
+         <entry><literal>{1}, {{1,2}}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>unnest</function>(<type>anyarray</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10421,10428 ****
     </note>
  
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function> for use with arrays.
     </para>
    </sect1>
  
--- 10601,10615 ----
     </note>
  
     <para>
+     In <function>array_sort</> and <function>set</> functions, input arrays are
+     always flattened into one-dimensional arrays. In addition, the lower bounds
+     of the returned arrays are adjusted to 1.
+    </para>
+ 
+    <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function>, <function>fusion</>,
!     and <function>intersection</> for use with arrays.
     </para>
    </sect1>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10468,10474 ****
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any
        </entry>
        <entry>
         array of the argument type
--- 10655,10661 ----
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any non-array
        </entry>
        <entry>
         array of the argument type
*************** SELECT NULLIF(value, '(none)') ...
*** 10606,10611 ****
--- 10793,10830 ----
       <row>
        <entry>
         <indexterm>
+         <primary>fusion</primary>
+        </indexterm>
+        <function>fusion(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>concatenation of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>intersection</primary>
+        </indexterm>
+        <function>intersection(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>intersection of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>max</primary>
         </indexterm>
         <function>max(<replaceable class="parameter">expression</replaceable>)</function>
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 79da185..df95fee 100644
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
*************** makeFuncExpr(Oid funcid, Oid rettype, Li
*** 454,459 ****
--- 454,474 ----
  }
  
  /*
+  * 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 456db5c..9418efa 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static RangeVar *makeRangeVarFromAnyName
*** 469,475 ****
   */
  
  /* 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
  
--- 469,475 ----
   */
  
  /* 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
*** 512,518 ****
  	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 NOREPLICATION_P
--- 512,518 ----
  	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 NOREPLICATION_P
*************** static RangeVar *makeRangeVarFromAnyName
*** 536,543 ****
  	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
--- 536,543 ----
  	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
*** 606,611 ****
--- 606,612 ----
  %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; }
*** 9620,9625 ****
--- 9621,9679 ----
  														 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 OF a_expr			%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP_ANY,
+ 							"=", $1, $4, @2);
+ 				}
+ 			| a_expr NOT MEMBER OF a_expr		%prec MEMBER
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP_ANY, "=",
+ 								$1, $5, @2), @2);
+ 				}
+ 			| a_expr SUBMULTISET OF a_expr		%prec SUBMULTISET
+ 				{
+ 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OP,
+ 							"<&", $1, $4, @2);
+ 				}
+ 			| a_expr NOT SUBMULTISET OF a_expr	%prec SUBMULTISET
+ 				{
+ 					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL,
+ 							(Node *) makeSimpleA_Expr(AEXPR_OP,
+ 								"<&", $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);
+ 				}
  		;
  
  /*
*************** ColLabel:	IDENT									{ $$ = $1; }
*** 11330,11336 ****
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
--- 11384,11391 ----
  /* "Unreserved" keywords --- available for use as any kind of name.
   */
  unreserved_keyword:
! 			  A
! 			| ABORT_P
  			| ABSOLUTE_P
  			| ACCESS
  			| ACTION
*************** unreserved_keyword:
*** 11457,11467 ****
--- 11512,11524 ----
  			| MAPPING
  			| MATCH
  			| MAXVALUE
+ 			| MEMBER
  			| MINUTE_P
  			| MINVALUE
  			| MODE
  			| MONTH_P
  			| MOVE
+ 			| MULTISET
  			| NAME_P
  			| NAMES
  			| NEXT
*************** unreserved_keyword:
*** 11549,11554 ****
--- 11606,11612 ----
  			| 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 499d357..c786b0d 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(ArrayType *array);
+ static void check_concatenable(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(result);
  		PG_RETURN_ARRAYTYPE_P(result);
  	}
  	if (PG_ARGISNULL(1))
  	{
  		result = PG_GETARG_ARRAYTYPE_P(0);
+ 		if (flatten)
+ 			result = array_flatten(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_concatenable(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(v2);
  		PG_RETURN_ARRAYTYPE_P(v2);
+ 	}
  
  	if (ndims2 == 0)
+ 	{
+ 		if (flatten)
+ 			v1 = array_flatten(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,1440 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		Return the number of elements in an 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 or slaces at end of an array. It is same as
+  *		array[array_lower(array, 1):array_upper(array, 1) - ntrimmed:.
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *result;
+ 	ArrayType  *v;
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	int			ndims;
+ 	int			nitems;
+ 	int		   *lower;
+ 	int		   *upper;
+ 	int			i;
+ 	int			arrtyplen;
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) must not be negative", ntrimmed)));
+ 
+ 	v = PG_GETARG_ARRAYTYPE_P(0);
+ 	ndims = ARR_NDIM(v);
+ 	nitems = (ndims == 0 ? 0 : ARR_DIMS(v)[0]);
+ 	if (ntrimmed > nitems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) is greater than number of elements (%d)", ntrimmed, nitems)));
+ 
+ 	lower = (int *) palloc(ndims * sizeof(int));
+ 	upper = (int *) palloc(ndims * sizeof(int));
+ 	for (i = 0; i < ndims; i++)
+ 	{
+ 		lower[i] = ARR_LBOUND(v)[i];
+ 		upper[i] = lower[i] - 1 + ARR_DIMS(v)[i];
+ 	}
+ 	/* remove elements from the first slice */
+ 	if (ndims > 0)
+ 		upper[0] -= ntrimmed;
+ 
+ 	arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0));
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	result = array_get_slice(
+ 		v, ndims, upper, lower, arrtyplen, elmlen, elmbyval, elmalign);
+ 
+ 	/* Adjust lower bound because array_get_slice reset the value to 1. */
+ 	for (i = 0; i < ndims; i++)
+ 		ARR_LBOUND(result)[i] = lower[i];
+ 
+ 	PG_FREE_IF_COPY(v, 0);
+ 	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_elements(const void *a, const void *b, void *arg)
+ {
+ 	return DatumGetInt32(FunctionCall2(
+ 		(FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements as an option.
+  */
+ static void
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 			  int nitems, int *nonnulls)
+ {
+ 	int			n,
+ 				i;
+ 
+ 	if (nulls == NULL)
+ 		n = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = n = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[n] = values[i];
+ 				nulls[n] = false;
+ 				n++;
+ 			}
+ 		}
+ 		for (i = n; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	qsort_arg(values, n, sizeof(Datum),
+ 			  compare_elements, &type->cmp_proc_finfo);
+ 
+ 	if (nonnulls)
+ 		*nonnulls = n;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. The values, nulls,
+  * nitems, and nonnulls parameters are modified directly. Note that only
+  * one null value will be kept in the result when there are some nulls.
+  */
+ static void
+ unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls,
+ 				TypeCacheEntry *type)
+ {
+ 	int		i,
+ 			n,
+ 			nvalues = *nonnulls;
+ 	bool	has_nulls = (*nonnulls < *nitems);
+ 
+ 	for (i = n = 1; i < nvalues; i++)
+ 	{
+ 		if (!DatumGetBool(FunctionCall2(
+ 			&type->eq_opr_finfo, values[i - 1], values[i])))
+ 		{
+ 			Assert(!nulls[n]);
+ 			values[n++] = values[i];
+ 		}
+ 	}
+ 	*nonnulls = n;
+ 	if (has_nulls)
+ 		nulls[n++] = true;
+ 	*nitems = n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements and sort them. Returns values,
+  * null, number of all elements and non-null elements as output parameters.
+  * nulls and nonnulls can be NULLs.
+  */
+ static void
+ deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls,
+ 					 int *nitems, int *nonnulls, TypeCacheEntry *type)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	bool	   *tmp_nulls;
+ 
+ 	AssertArg(values != NULL);
+ 	AssertArg(nitems != NULL);
+ 
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, &tmp_nulls, nitems);
+ 	sort_elements(type, *values, tmp_nulls, *nitems, nonnulls);
+ 
+ 	if (nulls)
+ 		*nulls = tmp_nulls;
+ }
+ 
+ /*
+  * A worker for array_sort, array_to_set, and higher-level functions.
+  */
+ static ArrayType *
+ sort_or_unique(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 	Datum		   *values;
+ 	bool		   *nulls;
+ 	int				nitems,
+ 					nonnulls;
+ 	int				lbs = 1;
+ 	Oid				element_type = ARR_ELEMTYPE(array);
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type);
+ 	if (unique)
+ 		unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 
+ 	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)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, false, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_to_set :
+  *		Remove duplicated elements in an array.
+  */
+ Datum
+ array_to_set(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = sort_or_unique(array, true, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * 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;
+ 	Datum	   *values;
+ 	int			nitems,
+ 				nonnulls;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	
+ 	type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type);
+ 	if (nitems > nonnulls + 1)
+ 	{
+ 		/* only one null is allowd */
+ 		result = false;
+ 	}
+ 	else
+ 	{
+ 		result = true;
+ 		/* compare for each adjacent */
+ 		for (i = 1; i < nonnulls; i++)
+ 		{
+ 			if (DatumGetBool(FunctionCall2(
+ 				&type->eq_opr_finfo, values[i - 1], values[i])))
+ 			{
+ 				result = false;
+ 				break;
+ 			}
+ 		}
+ 	}
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_BOOL(result);
+ }
+ 
+ static Datum
+ array_subset_internal(PG_FUNCTION_ARGS, int arg_contained, int arg_contains)
+ {
+ 	ArrayType  *v1 = NULL;
+ 	ArrayType  *v2 = NULL;
+ 	bool		result = false;
+ 	bool		result_null = false;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2,
+ 				n1,
+ 				n2,
+ 				unmatch;
+ 	TypeCacheEntry *type;
+ 
+ 	/* always null when v1 is null */
+ 	if (PG_ARGISNULL(arg_contained))
+ 	{
+ 		result_null = true;
+ 		goto ok;
+ 	}
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(arg_contained);
+ 	nitems1 = ArrayGetNItems(ARR_NDIM(v1), ARR_DIMS(v1));
+ 
+ 	/* null when v2 is null, but false when v1 is empty */
+ 	if (PG_ARGISNULL(arg_contains))
+ 	{
+ 		if (nitems1 == 0)
+ 			result = true;
+ 		else
+ 			result_null = true;
+ 		goto ok;
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(arg_contains);
+ 	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;
+ 	}
+ 
+ 	/* compare non-null elements */
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(v1, &values1, NULL, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, NULL, &nitems2, &nonnulls2, type);
+ 
+ 	unmatch = 0;
+ 	for (n1 = n2 = 0;
+ 		 n1 < nonnulls1 && unmatch <= nitems2 - nonnulls2 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			unmatch++;
+ 		if (r <= 0)
+ 			n1++;
+ 		if (r >= 0)
+ 			n2++;
+ 	}
+ 
+ 	unmatch += nonnulls1 - n1;
+ 	if (unmatch == 0 && nitems1 - nonnulls1 <= 0)
+ 		result = true;
+ 	else if (unmatch > nitems2 - nonnulls2)
+ 		result = false;
+ 	else
+ 		result_null = true;	/* v2 has equal or more nulls than unmatches */
+ 
+ ok:
+ 	if (v1 != NULL)
+ 		PG_FREE_IF_COPY(v1, arg_contained);
+ 	if (v2 != NULL)
+ 		PG_FREE_IF_COPY(v2, arg_contains);
+ 
+ 	if (result_null)
+ 		PG_RETURN_NULL();
+ 	else
+ 		PG_RETURN_BOOL(result);
+ }
+ 
+ /*
+  * array_subset : SUBMULTISET OF
+  *		Return true iff v1 is a subset of v2,
+  */
+ Datum
+ array_subset(PG_FUNCTION_ARGS)
+ {
+ 	return array_subset_internal(fcinfo, 0, 1);
+ }
+ 
+ /*
+  * array_superset
+  *		Commutator for SUBMULTISET OF
+  */
+ Datum
+ array_superset(PG_FUNCTION_ARGS)
+ {
+ 	return array_subset_internal(fcinfo, 1, 0);
+ }
+ 
+ /*
+  * array_union : MULTISET UNION [ DISTINCT | ALL ]
+  *		Concatinate two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ array_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_concatenable(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);
+ 
+ 	sort_elements(type, values, nulls, nitems, &nonnulls);
+ 	unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 	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;
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		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;
+ }
+ 
+ /*
+  * array_intersect : MULTISET INTERSECT [ DISTINCT | ALL ]
+  *		Intersection of two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ array_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,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	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);
+ }
+ 
+ /*
+  * array_except : MULTISET EXCEPT [ DISTINCT | ALL ]
+  *		Subtraction of two arrays, and optionally remove duplicated values.
+  */
+ Datum
+ array_except(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1;
+ 	ArrayType  *v2;
+ 	bool		all;
+ 	ArrayType  *result;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls1,
+ 			   *nulls2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	all = PG_GETARG_BOOL(2);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		if (all)
+ 			PG_RETURN_ARRAYTYPE_P(array_flatten(v1));
+ 
+ 		result = sort_or_unique(v1, false, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_concatenable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else
+ 			n2++;
+ 		if (r == 0)
+ 			n1++;
+ 	}
+ 	for (; n1 < nonnulls1; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); 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
+ array_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
+ array_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);
+ 		TypeCacheEntry *type;
+ 
+ 		type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (IntersectState *) palloc(sizeof(IntersectState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, &state->values, &state->nulls,
+ 								 &state->nitems, NULL, type);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatenable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type);
+ 			state->nitems = intersect_sorted_arrays(type,
+ 								state->values, state->nulls, state->nitems,
+ 								values, nulls, nitems);
+ 		}
+ 
+ 		PG_FREE_IF_COPY(v, 1);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ array_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.
+  * The lower bounds is adjusted to 1.
+  */
+ static ArrayType *
+ array_flatten(ArrayType *array)
+ {
+ 	ArrayType  *result;
+ 	int			ndims = ARR_NDIM(array);
+ 	int32		dataoffset;
+ 	int			ndatabytes,
+ 				nbytes;
+ 	int			nitems;
+ 
+ 	if (ndims < 1 || (ndims == 1 && ARR_LBOUND(array)[0] == 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_concatenable(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 26966d2..1e78c38 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2901 xmlconcat2	  -					0	
*** 222,227 ****
--- 222,229 ----
  
  /* array */
  DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3090	array_fusion_transfn array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3093	array_intersection_transfn array_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_operator.h b/src/include/catalog/pg_operator.h
index b69b60a..e76320c 100644
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DATA(insert OID = 2590 (  "|&>"    PGNSP
*** 867,872 ****
--- 867,874 ----
  DATA(insert OID = 2750 (  "&&"	   PGNSP PGUID b f f 2277 2277	16 2750  0 arrayoverlap areasel areajoinsel ));
  DATA(insert OID = 2751 (  "@>"	   PGNSP PGUID b f f 2277 2277	16 2752  0 arraycontains contsel contjoinsel ));
  DATA(insert OID = 2752 (  "<@"	   PGNSP PGUID b f f 2277 2277	16 2751  0 arraycontained contsel contjoinsel ));
+ DATA(insert OID = 3095 (  "&>"	   PGNSP PGUID b f f 2277 2277	16 3096  0 supermultiset_of contsel contjoinsel ));
+ DATA(insert OID = 3096 (  "<&"	   PGNSP PGUID b f f 2277 2277	16 3095  0 submultiset_of contsel contjoinsel ));
  
  /* capturing operators to preserve pre-8.3 behavior of text concatenation */
  DATA(insert OID = 2779 (  "||"	   PGNSP PGUID b f f 25 2776	25	 0 0 textanycat - - ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f8b5d4d..4449e58 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1062,1067 ****
--- 1062,1097 ----
  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 = 3079 (  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 = 3080 (  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 at end of array");
+ DATA(insert OID = 3081 (  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 = 3082 (  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 = 3083 (  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 = 3084 (  submultiset_of   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ array_subset _null_ _null_ _null_ ));
+ DESCR("contained as subset?");
+ DATA(insert OID = 3085 (  supermultiset_of PGNSP PGUID 12 1 0 0 f f f f f i 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ array_superset _null_ _null_ _null_ ));
+ DESCR("contains as subset?");
+ DATA(insert OID = 3086 (  multiset_union PGNSP PGUID 12 1 0 0 f f f f f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ array_union _null_ _null_ _null_ ));
+ DESCR("concatenate two arrays");
+ DATA(insert OID = 3087 (  multiset_intersect PGNSP PGUID 12 1 0 0 f f f t f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ array_intersect _null_ _null_ _null_ ));
+ DESCR("intersection of two arrays");
+ DATA(insert OID = 3088 (  multiset_except PGNSP PGUID 12 1 0 0 f f f f f i 3 0 2277 "2277 2277 16" _null_ _null_ _null_ _null_ array_except _null_ _null_ _null_ ));
+ DESCR("exception of two arrays");
+ DATA(insert OID = 3089 (  array_fusion_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_fusion_transfn _null_ _null_ _null_ ));
+ DESCR("fusion transition function");
+ DATA(insert OID = 3090 (  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 arrays into an array");
+ DATA(insert OID = 3091 (  array_intersection_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_intersection_transfn _null_ _null_ _null_ ));
+ DESCR("intersection transition function");
+ DATA(insert OID = 3092 (  array_intersection_finalfn PGNSP PGUID 12 1 0 0 f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_intersection_finalfn _null_ _null_ _null_ ));
+ DESCR("intersection final function");
+ DATA(insert OID = 3093 (  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 7c41312..b5cd584 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 578d3cd..5f59d22 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_
*** 358,363 ****
--- 361,367 ----
  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 7f7e744..450ab9d 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 281,285 ****
--- 281,298 ----
  
  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_sort(PG_FUNCTION_ARGS);
+ extern Datum array_to_set(PG_FUNCTION_ARGS);
+ extern Datum array_is_set(PG_FUNCTION_ARGS);
+ extern Datum array_subset(PG_FUNCTION_ARGS);
+ extern Datum array_superset(PG_FUNCTION_ARGS);
+ extern Datum array_union(PG_FUNCTION_ARGS);
+ extern Datum array_intersect(PG_FUNCTION_ARGS);
+ extern Datum array_except(PG_FUNCTION_ARGS);
+ extern Datum array_fusion_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_intersection_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_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 7b05ce3..deb47f6 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1558,1560 ****
--- 1558,1773 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- MULTISET support
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+  cardinality | cardinality 
+ -------------+-------------
+            3 |           4
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+  trim_array | trim_array |     trim_array     | trim_array 
+ ------------+------------+--------------------+------------
+  {1,2,3}    | {1}        | [-2:0]={-2,NULL,0} | {{1,2}}
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ ERROR:  number of trimmed elements (-1) must not be negative
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ ERROR:  number of trimmed elements (4) is greater than number of elements (3)
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+  ?column? | ?column? 
+ ----------+----------
+  f        | t
+ (1 row)
+ 
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 1, 2] SUBMULTISET OF ARRAY[1, 2, 2];
+  ?column? 
+ ----------
+  f
+ (1 row)
+ 
+ SELECT ARRAY['A', 'B', 'C'] SUBMULTISET OF ARRAY['A', 'B', 'D'];
+  ?column? 
+ ----------
+  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[];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF ARRAY[]::int[];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT NULL::int[] SUBMULTISET OF NULL::int[];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, 2],
+        ARRAY[1, NULL] SUBMULTISET OF ARRAY[2, 3];
+  ?column? | ?column? 
+ ----------+----------
+           | f
+ (1 row)
+ 
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+  ?column? | ?column? 
+ ----------+----------
+           | f
+ (1 row)
+ 
+ SELECT ARRAY[1, NULL] SUBMULTISET OF ARRAY[1, NULL];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 1, 2, 3];
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[1, 2, NULL];
+  ?column? 
+ ----------
+  
+ (1 row)
+ 
+ SELECT ARRAY[1, 2, 3] SUBMULTISET OF ARRAY[NULL, 4, NULL];
+  ?column? 
+ ----------
+  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,2,NULL}
+ (1 row)
+ 
+ SELECT ARRAY[2, NULL, 1, 2, NULL] MULTISET EXCEPT ARRAY[2, NULL];
+  multiset_except 
+ -----------------
+  {1}
+ (1 row)
+ 
+ SELECT fusion(a), intersection(a)
+   FROM (VALUES
+   (ARRAY[1, 2, 3, 2, 2]),
+   (ARRAY[1, 2, 4, 2]),
+   (ARRAY[[3, 2], [2, 1]])
+ ) AS t(a);
+            fusion            | intersection 
+ -----------------------------+--------------
+  {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 array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+   array_sort   
+ ---------------
+  {A,B,C,D,E,F}
+ (1 row)
+ 
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+      set      
+ --------------
+  {1,2,3,NULL}
+ (1 row)
+ 
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
+    set   
+ ---------
+  {A,B,C}
+ (1 row)
+ 
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 9ea53b1..422e90a 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 438,440 ****
--- 438,488 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- MULTISET support
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]), cardinality(ARRAY[[1, 2], [3, 4]]);
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   trim_array('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2),
+ 	   trim_array(ARRAY[[1, 2], [3, 4]], 1);
+ SELECT trim_array(ARRAY[1, 2, 3], -1);
+ SELECT trim_array(ARRAY[1, 2, 3], 4);
+ SELECT 'B' MEMBER OF ARRAY['A', 'B', 'C'];
+ SELECT 3 MEMBER OF ARRAY[1, 2], 3 MEMBER OF ARRAY[[1, 2], [3, 4]];
+ SELECT 3 NOT MEMBER OF ARRAY[1, 2];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[3, 2, 1];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[[1, 3], [2, 4]];
+ 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, 2],
+        ARRAY[1, NULL] SUBMULTISET OF ARRAY[2, 3];
+ SELECT ARRAY[1, 2] SUBMULTISET OF ARRAY[1, NULL],
+        ARRAY[1, 2] SUBMULTISET OF ARRAY[3, NULL];
+ 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 fusion(a), intersection(a)
+   FROM (VALUES
+   (ARRAY[1, 2, 3, 2, 2]),
+   (ARRAY[1, 2, 4, 2]),
+   (ARRAY[[3, 2], [2, 1]])
+ ) AS t(a);
+ SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT array_sort(ARRAY[['A', 'D'], ['B', 'E'], ['C', 'F']]);
+ SELECT set(ARRAY[1, 3, 2, 3, NULL, 1, NULL]);
+ SELECT set(ARRAY[['A', 'C'], ['B', 'C'], ['C', 'A']]);
#24Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#23)
Re: multiset patch review

On Mon, Jan 31, 2011 at 1:49 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

I removed collect() aggregate function because the result type is MULTISET
in the spec. I keep all of other functions and operators because they won't
break anything in the standard. When we will have true MULTISET data type,
we can overload those functions and operators for MULTISET and ARRAY.

I am still not in favor of adding this syntax. I'd be in favor of
adding array_cardinality(), trim_array(), array_sort(), and
array_flatten(). [It sure is awkward that trim_array has the word
array second and all of our other array functions have it first - is
this required by the spec?]

array_to_set() and array_is_set() seem possibly useful, but I probably
would have called them array_remove_dups() and array_has_dups(). I
might be in the minority on that one, though.

I think array_subset(), array_union(), array_intersect(), and
array_except() are useful, but I think they should just be regular
functions, without any special syntax.

fusion() and intersection() also seem useful, but maybe it would be
more consistent to all them array_fusion() and array_intersection().

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#25Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#24)
Re: multiset patch review

On Sat, Feb 5, 2011 at 02:29, Robert Haas <robertmhaas@gmail.com> wrote:

I am still not in favor of adding this syntax.

I also don't like the syntax, but unfortunately, almost all of
them are in the SQL standard :-(. In addition, Oracle already
uses the same feature with the special syntax, though true multiset
data types are supported in it.

[It sure is awkward that trim_array has the word
array second and all of our other array functions have it first - is
this required by the spec?]

Yes, again, but we could have array_trim() for the alias.

array_to_set() and array_is_set() seem possibly useful, but I probably
would have called them array_remove_dups() and array_has_dups().  I
might be in the minority on that one, though.

array_to_set is the only function we can rename freely.
Another candidates might be array_unique (contrib/intarray uses uniq).

array_is_set() is an internal representation of "IS A SET" operator.
So, the name is not so important (and not documented.)

I think array_subset(), array_union(), array_intersect(), and
array_except() are useful, but I think they should just be regular
functions, without any special syntax.

All of the special syntax are in the spec, except the argument
types should be multisets rather than arrays.

fusion() and intersection() also seem useful, but maybe it would be
more consistent to all them array_fusion() and array_intersection().

Both of the names are the standard... We could have array_fusion()
for array types and fusion() for multiset types, but I prefer
overloaded fusion() to have both names.

--
Itagaki Takahiro

#26Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#25)
Re: multiset patch review

On Fri, Feb 4, 2011 at 1:02 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Sat, Feb 5, 2011 at 02:29, Robert Haas <robertmhaas@gmail.com> wrote:

I am still not in favor of adding this syntax.

I also don't like the syntax, but unfortunately, almost all of
them are in the SQL standard :-(.

The standard specifies this syntax for arrays, or for multisets?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#27Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Robert Haas (#26)
Re: multiset patch review

On Sat, Feb 5, 2011 at 03:04, Robert Haas <robertmhaas@gmail.com> wrote:

I am still not in favor of adding this syntax.

I also don't like the syntax, but unfortunately, almost all of
them are in the SQL standard :-(.

The standard specifies this syntax for arrays, or for multisets?

Multisets. But I chose the same function name and syntax because
arrays *are* multisets by definition.

--
Itagaki Takahiro

#28Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#27)
Re: multiset patch review

On Fri, Feb 4, 2011 at 1:15 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Sat, Feb 5, 2011 at 03:04, Robert Haas <robertmhaas@gmail.com> wrote:

I am still not in favor of adding this syntax.

I also don't like the syntax, but unfortunately, almost all of
them are in the SQL standard :-(.

The standard specifies this syntax for arrays, or for multisets?

Multisets. But I chose the same function name and syntax because
arrays *are* multisets by definition.

In math class, maybe. But in programming, no. Multiset is a
datatype. Array is a different datatype. There is no reason why we
need to clutter our parser with extra keywords to support a
non-standard feature extension.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#29Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Robert Haas (#28)
Re: multiset patch review

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Feb 4, 2011 at 1:15 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

Multisets. But I chose the same function name and syntax because
arrays *are* multisets by definition.

In math class, maybe. But in programming, no. Multiset is a
datatype. Array is a different datatype. There is no reason why we
need to clutter our parser with extra keywords to support a
non-standard feature extension.

My understanding is that we will have to have those functions defined
and user visible, and that we benefit from function overloading which is
not in the standard. So there's no reason not to provide those function
for arrays already, then extend to full multiset support.

Given PostgreSQL overloading, yes, arrays are multisets as far as
defining those standard compliant APIs is concerned. AFAIUI.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#30Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Dimitri Fontaine (#29)
Re: multiset patch review

On Sat, Feb 5, 2011 at 04:24, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:

In math class, maybe.  But in programming, no.  Multiset is a
datatype.  Array is a different datatype.  There is no reason why we
need to clutter our parser with extra keywords to support a
non-standard feature extension.

My understanding is that we will have to have those functions defined
and user visible, and that we benefit from function overloading which is
not in the standard.  So there's no reason not to provide those function
for arrays already, then extend to full multiset support.

Given PostgreSQL overloading, yes, arrays are multisets as far as
defining those standard compliant APIs is concerned.  AFAIUI.

Yes, I'd like to use overloading.
Choosing arbitrary names increases learning costs for users.

--
Itagaki Takahiro

#31Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#30)
Re: multiset patch review

On Fri, Feb 4, 2011 at 9:11 PM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

On Sat, Feb 5, 2011 at 04:24, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:

In math class, maybe.  But in programming, no.  Multiset is a
datatype.  Array is a different datatype.  There is no reason why we
need to clutter our parser with extra keywords to support a
non-standard feature extension.

My understanding is that we will have to have those functions defined
and user visible, and that we benefit from function overloading which is
not in the standard.  So there's no reason not to provide those function
for arrays already, then extend to full multiset support.

Given PostgreSQL overloading, yes, arrays are multisets as far as
defining those standard compliant APIs is concerned.  AFAIUI.

Yes, I'd like to use overloading.
Choosing arbitrary names increases learning costs for users.

Right, but making the parser slower has a cost, too.
ScanKeywordLookup() is already a hotspot in some workloads, and
there's overhead buried in the bison parser, too. I think it's a big
mistake to get into the business of adding keywords just so we can
provide an alternative syntax to call a function. Many people who use
these functions will never even have heard of the MULTISET stuff that
is part of the spec, and even those that have can figure out our
alternatives by spending five minutes with the documentation. I find
it really difficult to accept that it is worth slowing down parsing
for the 95% of users who are not going to use these functions to
provide a slightly nicer API for the ones that do.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#32Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#31)
Re: multiset patch review

Robert Haas <robertmhaas@gmail.com> writes:

Right, but making the parser slower has a cost, too.
ScanKeywordLookup() is already a hotspot in some workloads, and
there's overhead buried in the bison parser, too.

Yeah. Keep in mind that a bison parser fundamentally runs off a
two-dimensional array: one axis is parser state and the other is token
number. They have some tricks to compress the array a bit, but adding
keywords contributes directly to a bigger array, which means slower
parsing (more L1 cache misses). The parser's inner loop frequently
shows up as a hotspot in profiles I do, and I think that has to be more
about the amount of data it's touching than the cost of the loop per se.

Adding unnecessary keywords is something to be avoided.

regards, tom lane

#33Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Tom Lane (#32)
Re: multiset patch review

On Sat, Feb 12, 2011 at 00:50, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Right, but making the parser slower has a cost, too.
ScanKeywordLookup() is already a hotspot in some workloads, and
there's overhead buried in the bison parser, too.

Yeah.  Keep in mind that a bison parser fundamentally runs off a
two-dimensional array: one axis is parser state and the other is token
number.  They have some tricks to compress the array a bit, but adding
keywords contributes directly to a bigger array, which means slower
parsing (more L1 cache misses).  The parser's inner loop frequently
shows up as a hotspot in profiles I do, and I think that has to be more
about the amount of data it's touching than the cost of the loop per se.

Did you measure the actual cost in the real world? If we are using
such a sensitive parser, it should be a problem even without the patch.

Adding unnecessary keywords is something to be avoided.

Our conclusion is "we never support multiset syntax in the SQL standard",
right? If we think they are unnecessary, we cannot support it.

I will remove parser changes from the patch; it will add only a few array
functions. Then, please let me know functions you don't want to include
in the core, if any. I'll remove them at the same time.

--
Itagaki Takahiro

#34Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#33)
Re: multiset patch review

On Fri, Feb 11, 2011 at 11:17 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

Did you measure the actual cost in the real world? If we are using
such a sensitive parser, it should be a problem even without the patch.

It *is* a problem without the patch!

Adding unnecessary keywords is something to be avoided.

Our conclusion is "we never support multiset syntax in the SQL standard",
right?  If we think they are unnecessary, we cannot support it.

No, my conclusion is that if we're not really doing what the standard
says anyway, it's not worth the cost of the new keywords. Whether or
not we'd be willing to pay the cost for an implementation that
actually conformed with the standard is not something I believe we've
decided. But it's not going to happen any time soon, because adding
actual multiset types would bloat pg_type by another 50%, which is a
cost I doubt that we will be willing to pay. I really hope someone
will eventually fix things so that we don't need to add a new copy of
every composite type definition for every collection type we want to
support, but until that happens, there is not much chance of
implementing this feature in a way that is actually
standard-conforming. And until then, paying the extra parsing cost
for something that isn't going to be standard behavior anyway is not a
good trade-off.

We have spent countless hours figuring out how to redesign bits of
syntax so that they could use already-existing keywords instead of
adding new ones; and many more hours angsting about whether there is
any way to get rid of some of the keywords we already have. The new
options syntax for EXPLAIN and VACUUM exists *primarily* to reduce the
number of future keywords we'll need to create. This is a seriously
annoying problem, but it's not one we made up just for this patch. We
deal with it all the time. Do we sometimes add keywords? Sure, of
course we do. But we try to minimize it. It isn't free.

I will remove parser changes from the patch; it will add only a few array
functions. Then, please let me know functions you don't want to include
in the core, if any. I'll remove them at the same time.

I posted my thoughts on this topic a week ago.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#35Stephen Frost
sfrost@snowman.net
In reply to: Itagaki Takahiro (#33)
Re: multiset patch review

* Itagaki Takahiro (itagaki.takahiro@gmail.com) wrote:

I will remove parser changes from the patch; it will add only a few array
functions. Then, please let me know functions you don't want to include
in the core, if any. I'll remove them at the same time.

Seems like this should be 'waiting on author', but since it was marked
'needs review', I started taking a look at it. Without the grammar
changes, it's just adding a couple of functions. In general, I'm all
for that, but I don't like this:

Input arrays are always flattened into one-dimensional arrays.

That just strikes me as completely broken when it comes to PG Arrays.
What does the spec say about this, if anything? Is that required by
spec, or is the spec not relevant to this because MULTISETs are only one
dimensional..?

I would think that we would have a way to define what dimension or piece
of the array that would be sorted or returned as a set, etc. I could
see having a 'flatten' function which could be called first, if that's
really what you want.. Or maybe we just need a slice function whose
result could then be passed in to these functions, perhaps..

In my view, we should be throwing an error if we get called on a
multi-dim array and we can't perform the operation on that in an
obviously correct way, not forcing the input to match something we can
make work.

From above, that makes me not thrilled with the 'flatten' boolean for
array_cat_internal(), nor with how it was implemented, or how those
changes apparently didn't justfiy *any* comment updates..

I'm not thrilled with called ArrayGetNItems array_cardinality(). Why
not just provide a function with a name like "array_getnitems()"
instead?

trim_array() suffers from two problems: lack of comments, and no spell
checking done on those that are there.

Should get_type_cache() really live in array_userfuncs.c ?

There's more, primairly lack of comments and what I consider poor
function naming ("sort_or_unique()" ? really?), but in the end my
feeling is that this could survive just fine, for now, as a contrib
module, and that it really isn't close enough to being committable to
make it into 9.1 in any case.

I'll mark it waiting on author, since I think the formal 'returned with
feedback' needs to come from someone else, but that's where I think this
is headed.

Thanks,

Stephen

#36Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Stephen Frost (#35)
Re: multiset patch review

On Sat, Feb 12, 2011 at 05:01, Stephen Frost <sfrost@snowman.net> wrote:

Input arrays are always flattened into one-dimensional arrays.
That just strikes me as completely broken when it comes to PG Arrays.

Contains operators (<@, &&, @>) ignore multi-dimensions.
Array slice operator ([lo:hi]) always reset the index offsets.

What does the spec say about this, if anything?  Is that required by
spec, or is the spec not relevant to this because MULTISETs are only one
dimensional..?

Yes. The SQL standard has only one-dimensional ARRAYs and MULTISETs ,
though it supports "collections of collections", that we don't have.

I would think that we would have a way to define what dimension or piece
of the array that would be sorted or returned as a set, etc.

It would be consistent if we return (ARRAY[][])[] => ARRAY[],
but we throw errors actually.

In my view, we should be throwing an error if we get called on a
multi-dim array and we can't perform the operation on that in an
obviously correct way, not forcing the input to match something we can
make work.

Agreed, I'll do so. We could also keep lower-bounds of arrays,
at least on sort.

I'm not thrilled with called ArrayGetNItems array_cardinality().  Why
not just provide a function with a name like "array_getnitems()"
instead?

We must use the name, because it is in the SQL standard.
FUNCTION cardinality(collection) => number
We would have overloaded two versions for ARRAYs andMULTISETs.

trim_array() suffers from two problems: lack of comments, and no spell
checking done on those that are there.

What comments do you want?

Should get_type_cache() really live in array_userfuncs.c ?

Do you find codes using the same operation in other files?

There's more, primairly lack of comments and what I consider poor
function naming ("sort_or_unique()" ?  really?),

Could you suggest better names?

--
Itagaki Takahiro

#37Stephen Frost
sfrost@snowman.net
In reply to: Itagaki Takahiro (#36)
Re: multiset patch review

Itagaki,

* Itagaki Takahiro (itagaki.takahiro@gmail.com) wrote:

On Sat, Feb 12, 2011 at 05:01, Stephen Frost <sfrost@snowman.net> wrote:

What does the spec say about this, if anything?  Is that required by
spec, or is the spec not relevant to this because MULTISETs are only one
dimensional..?

Yes. The SQL standard has only one-dimensional ARRAYs and MULTISETs ,
though it supports "collections of collections", that we don't have.

Yeah, I was afraid of that.. :(

In my view, we should be throwing an error if we get called on a
multi-dim array and we can't perform the operation on that in an
obviously correct way, not forcing the input to match something we can
make work.

Agreed, I'll do so. We could also keep lower-bounds of arrays,
at least on sort.

Sounds good. I'm also fine with providing a 'flatten' function, I just
don't agree w/ doing it automatically.

I'm not thrilled with called ArrayGetNItems array_cardinality().  Why
not just provide a function with a name like "array_getnitems()"
instead?

We must use the name, because it is in the SQL standard.

If we use the name, then we have to throw an error when it's not a
single dimension array, since that's what's in the SQL standard. In
that case, we might as well have another function which gives us
ArrayGetNItems anyway.

trim_array() suffers from two problems: lack of comments, and no spell
checking done on those that are there.

What comments do you want?

Uhm, how about ones that explain what's going on in each paragraph of
code..? And in other places, commenting the functions, what they do,
what they're used for, and documenting each of the arguments that are
passed in..

Should get_type_cache() really live in array_userfuncs.c ?

Do you find codes using the same operation in other files?

Not yet, but logically it's about gathering information about types and
could be needed beyond just arrays..

There's more, primairly lack of comments and what I consider poor
function naming ("sort_or_unique()" ?  really?),

Could you suggest better names?

How about 'array_sort()' or similar? With appropriate arguments that
can be used to request unique'ing or not? Or is there a "just unique
it, but don't sort it" option for this function?

Thanks,

Stephen

#38Itagaki Takahiro
itagaki.takahiro@gmail.com
In reply to: Stephen Frost (#37)
1 attachment(s)
Re: multiset patch review

Here is a cleanup version of multiset function patch. But all syntax
for multiset has been removed, so the patch just adds additional array
functions and aggregations.

=== The standard-compatible functions ===
- cardinality(anyarray)
- trim_array(anyarray, integer)
=== Renamed version of MULTISET functions ===
- array_trim(anyarray, integer) <= alias to trim_array()
- array_sort(anyarray)
- array_unique(anyarray)
- array_is_unique(anyarray)
- array_union(anyarray, anyarray)
- array_union_all(anyarray, anyarray) <= alias to array_cat()
- array_intersect(anyarray, anyarray)
- array_intersect_all(anyarray, anyarray)
- array_except(anyarray, anyarray)
- array_except_all(anyarray, anyarray)
- array_fusion(SETOF anyarray)
- array_intersection(SETOF anyarray)

On Sat, Feb 5, 2011 at 02:29, Robert Haas <robertmhaas@gmail.com> wrote:

I am still not in favor of adding this syntax.  I'd be in favor of
adding array_cardinality(), trim_array(), array_sort(), and
array_flatten().  [It sure is awkward that trim_array has the word
array second and all of our other array functions have it first - is
this required by the spec?]

array_flatten() no longer exists. I added array_trim() as an alias
to trim_array() because it would be a FAQ.

array_to_set() and array_is_set() seem possibly useful, but I probably
would have called them array_remove_dups() and array_has_dups().  I
might be in the minority on that one, though.

I prefer array_unique() and array_is_unique(), but will change
if objections.

I think array_subset(), array_union(), array_intersect(), and
array_except() are useful, but I think they should just be regular
functions, without any special syntax.

I removed array_subset(). It might be re-added with index support in
the future. We need more discussions to have different definition of
contains/subset operators (<@ vs. SUBMULTISET OF-like operator) .

All special syntax are removed. I split those set-operation functions
into "all" and "non-all" versions. So, we will have:
array_union[_all], array_intersect[_all], and array_except[_all].

fusion() and intersection() also seem useful, but maybe it would be
more consistent to all them array_fusion() and array_intersection().

Renamed. One issue might be array_intersect (normal function) and
array_intersection (aggregate function) have similar names.

On Sat, Feb 12, 2011 at 22:27, Stephen Frost <sfrost@snowman.net> wrote:

I'm not thrilled with called ArrayGetNItems array_cardinality().

We must use the name, because it is in the SQL standard.

If we use the name, then we have to throw an error when it's not a
single dimension array, since that's what's in the SQL standard.

Hmmm, in my understanding, we can define cardinality()
as "number of elements", that is reasonable for multi-dimensional arrays.

Other functions except cardinality() and trim_array() raise errors
if multi-dimensional arrays are passed. Also, trim_array(), array_sort(),
and array_unique() now keep lower bounds of the input arrays.

Should get_type_cache() really live in array_userfuncs.c ?

I think storing cache information in fcinfo is not so general solution.
So, the subroutine is still kept as a static function. Other comments
and variable names you suggested in the revised patch. Thanks.

--
Itagaki Takahiro

Attachments:

array_functions-20110215.patchapplication/octet-stream; name=array_functions-20110215.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 70a1bd9..7347de1 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT NULLIF(value, '(none)') ...
*** 10214,10222 ****
--- 10214,10237 ----
      <primary>array_dims</primary>
    </indexterm>
    <indexterm>
+     <primary>array_except</primary>
+   </indexterm>
+   <indexterm>
+     <primary>array_except_all</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_fill</primary>
    </indexterm>
    <indexterm>
+     <primary>array_intersect</primary>
+   </indexterm>
+   <indexterm>
+     <primary>array_intersect_all</primary>
+   </indexterm>
+   <indexterm>
+     <primary>array_is_unique</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_length</primary>
    </indexterm>
    <indexterm>
*************** SELECT NULLIF(value, '(none)') ...
*** 10226,10240 ****
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
!  <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
--- 10241,10273 ----
      <primary>array_prepend</primary>
    </indexterm>
    <indexterm>
+     <primary>array_sort</primary>
+   </indexterm>
+   <indexterm>
      <primary>array_to_string</primary>
    </indexterm>
!   <indexterm>
!     <primary>array_union</primary>
!   </indexterm>
!   <indexterm>
!     <primary>array_union_all</primary>
!   </indexterm>
!   <indexterm>
!     <primary>array_unique</primary>
!   </indexterm>
!   <indexterm>
      <primary>array_upper</primary>
    </indexterm>
    <indexterm>
+     <primary>cardinality</primary>
+   </indexterm>
+   <indexterm>
      <primary>string_to_array</primary>
    </indexterm>
    <indexterm>
+     <primary>trim_array</primary>
+   </indexterm>
+   <indexterm>
      <primary>unnest</primary>
    </indexterm>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10298,10303 ****
--- 10331,10358 ----
         <row>
          <entry>
           <literal>
+           <function>array_except</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>subtraction of two arrays and remove duplicated values</entry>
+         <entry><literal>array_except(ARRAY[1,1,2], ARRAY[1,3])</literal></entry>
+         <entry><literal>{2}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>array_except_all</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>subtraction of two arrays</entry>
+         <entry><literal>array_except_all(ARRAY[1,1,2], ARRAY[1,3])</literal></entry>
+         <entry><literal>{1,2}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_fill</function>(<type>anyelement</type>, <type>int[]</type>,
            <optional>, <type>int[]</type></optional>)
           </literal>
*************** SELECT NULLIF(value, '(none)') ...
*** 10311,10316 ****
--- 10366,10404 ----
         <row>
          <entry>
           <literal>
+           <function>array_intersect</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>intersection of two arrays and remove duplicated values</entry>
+         <entry><literal>array_intersect(ARRAY[1,1,2], ARRAY[1,1,3])</literal></entry>
+         <entry><literal>{1}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>array_intersect_all</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>intersection of two arrays</entry>
+         <entry><literal>array_intersect_all(ARRAY[1,1,2], ARRAY[1,1,3])</literal></entry>
+         <entry><literal>{1,1}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>array_is_unique</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>bool</type></entry>
+         <entry>returns true if no duplicated elements in an array</entry>
+         <entry><literal>array_is_unique(ARRAY[1,NULL]), array_is_unique(ARRAY[1,NULL,NULL])</literal></entry>
+         <entry><literal>t, f</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_length</function>(<type>anyarray</type>, <type>int</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10344,10349 ****
--- 10432,10448 ----
         <row>
          <entry>
           <literal>
+           <function>array_sort</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>sort elements in an array in ascending order</entry>
+         <entry><literal>array_sort(ARRAY[3,2,NULL,1])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_to_string</function>(<type>anyarray</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10356,10361 ****
--- 10455,10493 ----
         <row>
          <entry>
           <literal>
+           <function>array_union</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>text</type></entry>
+         <entry>union of two arrays and remove duplicated values</entry>
+         <entry><literal>array_union(ARRAY[2,3], ARRAY[1,1,2])</literal></entry>
+         <entry><literal>{1,2,3}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>array_union_all</function>(<type>anyarray</type>, <type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>text</type></entry>
+         <entry>union of two arrays, same as <function>array_cat</function></entry>
+         <entry><literal>array_union_all(ARRAY[2,3], ARRAY[1,1,2])</literal></entry>
+         <entry><literal>{2,3,1,1,2}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>array_unique</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove duplicated elements in an array</entry>
+         <entry><literal>array_unique(ARRAY[1,3,2,3,NULL,1,NULL])</literal></entry>
+         <entry><literal>{1,2,3,NULL}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>array_upper</function>(<type>anyarray</type>, <type>int</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10367,10372 ****
--- 10499,10515 ----
         <row>
          <entry>
           <literal>
+           <function>array_trim</function>(<type>anyarray</type>, <type>integer</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>remove supplied number of elements at end of an array</entry>
+         <entry><literal>array_trim(ARRAY[1,2,3], 2), array_trim(ARRAY[[1,2],[3,4]], 1)</literal></entry>
+         <entry><literal>{1}, {{1,2}}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>string_to_array</function>(<type>text</type>, <type>text</type> <optional>, <type>text</type></optional>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10379,10384 ****
--- 10522,10549 ----
         <row>
          <entry>
           <literal>
+           <function>cardinality</function>(<type>anyarray</type>)
+          </literal>
+         </entry>
+         <entry><type>int</type></entry>
+         <entry>returns the number of elements in an array</entry>
+         <entry><literal>cardinality(ARRAY[1,2,3])</literal></entry>
+         <entry><literal>3</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
+           <function>trim_array</function>(<type>anyarray</type>, <type>integer</type>)
+          </literal>
+         </entry>
+         <entry><type>anyarray</type></entry>
+         <entry>same as <function>array_trim</function></entry>
+         <entry><literal>trim_array(ARRAY[1,2,3], 2), trim_array(ARRAY[[1,2],[3,4]], 1)</literal></entry>
+         <entry><literal>{1}, {{1,2}}</literal></entry>
+        </row>
+        <row>
+         <entry>
+          <literal>
            <function>unnest</function>(<type>anyarray</type>)
           </literal>
          </entry>
*************** SELECT NULLIF(value, '(none)') ...
*** 10420,10428 ****
      </para>
     </note>
  
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function> for use with arrays.
     </para>
    </sect1>
  
--- 10585,10605 ----
      </para>
     </note>
  
+    <note>
+     <para>
+      Multidimensional arrays are not supported by
+      <function>array_except</>, <function>array_except_all</>,
+      <function>array_intersect</>, <function>array_intersect_all</>,
+      <function>array_is_unique</>, <function>array_sort</>,
+      <function>array_union</>, <function>array_union_all</>,
+      and <function>array_unique</> in the current version.
+     </para>
+    </note>
+ 
     <para>
      See also <xref linkend="functions-aggregate"> about the aggregate
!     function <function>array_agg</function>, <function>array_fusion</>,
!     and <function>array_intersection</> for use with arrays.
     </para>
    </sect1>
  
*************** SELECT NULLIF(value, '(none)') ...
*** 10468,10474 ****
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any
        </entry>
        <entry>
         array of the argument type
--- 10645,10651 ----
         <function>array_agg(<replaceable class="parameter">expression</replaceable>)</function>
        </entry>
        <entry>
!        any non-array
        </entry>
        <entry>
         array of the argument type
*************** SELECT NULLIF(value, '(none)') ...
*** 10479,10484 ****
--- 10656,10693 ----
       <row>
        <entry>
         <indexterm>
+         <primary>array_fusion</primary>
+        </indexterm>
+        <function>array_fusion(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>concatenation of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
+         <primary>array_intersection</primary>
+        </indexterm>
+        <function>array_intersection(<replaceable class="parameter">expression</replaceable>)</function>
+       </entry>
+       <entry>
+        any array
+       </entry>
+       <entry>
+        same as argument type
+       </entry>
+       <entry>intersection of input arrays</entry>
+      </row>
+ 
+      <row>
+       <entry>
+        <indexterm>
          <primary>average</primary>
         </indexterm>
         <indexterm>
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c
index 499d357..9b3276f 100644
*** a/src/backend/utils/adt/array_userfuncs.c
--- b/src/backend/utils/adt/array_userfuncs.c
***************
*** 15,21 ****
--- 15,24 ----
  #include "utils/array.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/typcache.h"
  
+ static void check_concatenable(Oid element_type1, Oid element_type2);
+ static void check_comparable(Oid element_type1, Oid element_type2);
  
  /*-----------------------------------------------------------------------------
   * array_push :
*************** 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;
--- 221,227 ----
  	element_type2 = ARR_ELEMTYPE(v2);
  
  	/* Check we have matching element types */
! 	check_concatenable(element_type1, element_type2);
  
  	/* OK, use it */
  	element_type = element_type1;
*************** array_agg_finalfn(PG_FUNCTION_ARGS)
*** 544,546 ****
--- 540,1328 ----
  
  	PG_RETURN_DATUM(result);
  }
+ 
+ /*
+  * array_cardinality :
+  *		Return the number of elements in an array. If the array is
+  *		multi-demensional, the total number of elements is returned.
+  */
+ 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 or slaces at end of an array. It is same as
+  *		array[array_lower(array, 1):array_upper(array, 1) - ntrimmed].
+  */
+ Datum
+ trim_array(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *result;
+ 	ArrayType  *v;
+ 	int32		ntrimmed = PG_GETARG_INT32(1);
+ 	int			ndims;
+ 	int			nitems;
+ 	int			lower[MAXDIM];
+ 	int			upper[MAXDIM];
+ 	int			i;
+ 	int			arrtyplen;
+ 	Oid			elmtype;
+ 	int16		elmlen;
+ 	bool		elmbyval;
+ 	char		elmalign;
+ 
+ 	if (ntrimmed < 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) must not be negative", ntrimmed)));
+ 
+ 	v = PG_GETARG_ARRAYTYPE_P(0);
+ 	ndims = ARR_NDIM(v);
+ 
+ 	/* Trimmed elements must not be greater than the first dimension length. */
+ 	nitems = (ndims == 0 ? 0 : ARR_DIMS(v)[0]);
+ 	if (ntrimmed > nitems)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_ARRAY_ELEMENT_ERROR),
+ 				 errmsg("number of trimmed elements (%d) is greater than number of elements (%d)", ntrimmed, nitems)));
+ 
+ 	/* Save lower and upper bounds to restore them at end. */
+ 	for (i = 0; i < ndims; i++)
+ 	{
+ 		lower[i] = ARR_LBOUND(v)[i];
+ 		upper[i] = lower[i] - 1 + ARR_DIMS(v)[i];
+ 	}
+ 
+ 	/* Remove elements from the first dimension and get a new slice. */
+ 	if (ndims > 0)
+ 		upper[0] -= ntrimmed;
+ 	arrtyplen = get_typlen(get_fn_expr_argtype(fcinfo->flinfo, 0));
+ 	elmtype = ARR_ELEMTYPE(v);
+ 	get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
+ 	result = array_get_slice(
+ 		v, ndims, upper, lower, arrtyplen, elmlen, elmbyval, elmalign);
+ 
+ 	/* Adjust lower bounds because array_get_slice reset the value to 1. */
+ 	for (i = 0; i < ndims; i++)
+ 		ARR_LBOUND(result)[i] = lower[i];
+ 
+ 	PG_FREE_IF_COPY(v, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_trim :
+  *		An alias for trim_array for naming consistency.
+  */
+ Datum
+ array_trim(PG_FUNCTION_ARGS)
+ {
+ 	return trim_array(fcinfo);
+ }
+ 
+ /*
+  * 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_elements(const void *a, const void *b, void *arg)
+ {
+ 	return DatumGetInt32(FunctionCall2(
+ 		(FmgrInfo *) arg, *(const Datum *) a, *(const Datum *) b));
+ }
+ 
+ /*
+  * Sort values and move nulls to the end.
+  * Returns number of non-null elements as an option.
+  */
+ static void
+ sort_elements(TypeCacheEntry *type, Datum *values, bool *nulls,
+ 			  int nitems, int *nonnulls)
+ {
+ 	int			n,
+ 				i;
+ 
+ 	if (nulls == NULL)
+ 		n = nitems;
+ 	else
+ 	{
+ 		/* move nulls to end of the array */
+ 		for (i = n = 0; i < nitems; i++)
+ 		{
+ 			if (!nulls[i])
+ 			{
+ 				values[n] = values[i];
+ 				nulls[n] = false;
+ 				n++;
+ 			}
+ 		}
+ 		for (i = n; i < nitems; i++)
+ 			nulls[i] = true;
+ 	}
+ 
+ 	/* sort non-null values */
+ 	qsort_arg(values, n, sizeof(Datum),
+ 			  compare_elements, &type->cmp_proc_finfo);
+ 
+ 	if (nonnulls)
+ 		*nonnulls = n;
+ }
+ 
+ /*
+  * Remove duplicated values in already sorted elements. The values, nulls,
+  * nitems, and nonnulls parameters are modified directly. Note that only
+  * one null value will be kept in the result when there are some nulls.
+  */
+ static void
+ unique_elements(Datum *values, bool *nulls, int *nitems, int *nonnulls,
+ 				TypeCacheEntry *type)
+ {
+ 	int		i,
+ 			n,
+ 			nvalues = *nonnulls;
+ 	bool	has_nulls = (*nonnulls < *nitems);
+ 
+ 	for (i = n = 1; i < nvalues; i++)
+ 	{
+ 		if (!DatumGetBool(FunctionCall2(
+ 			&type->eq_opr_finfo, values[i - 1], values[i])))
+ 		{
+ 			Assert(!nulls[n]);
+ 			values[n++] = values[i];
+ 		}
+ 	}
+ 	*nonnulls = n;
+ 	if (has_nulls)
+ 		nulls[n++] = true;
+ 	*nitems = n;
+ }
+ 
+ /*
+  * Deconstruct an array to a list of elements and sort them. Returns values,
+  * null, number of all elements and non-null elements as output parameters.
+  * nulls and nonnulls can be NULLs.
+  */
+ static void
+ deconstruct_and_sort(ArrayType *array, Datum **values, bool **nulls,
+ 					 int *nitems, int *nonnulls, TypeCacheEntry *type)
+ {
+ 	Oid			element_type = ARR_ELEMTYPE(array);
+ 	bool	   *tmp_nulls;
+ 
+ 	AssertArg(values != NULL);
+ 	AssertArg(nitems != NULL);
+ 
+ 	/* TODO: Currently we do not support multi-dimensional arrays. */
+ 	if (ARR_NDIM(array) > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("cannot use multi-dimensional arrays")));
+ 
+ 	deconstruct_array(array,
+ 					  element_type,
+ 					  type->typlen,
+ 					  type->typbyval,
+ 					  type->typalign,
+ 					  values, &tmp_nulls, nitems);
+ 	sort_elements(type, *values, tmp_nulls, *nitems, nonnulls);
+ 
+ 	if (nulls)
+ 		*nulls = tmp_nulls;
+ }
+ 
+ /*
+  * A worker for array_sort, array_unique, and other functions.
+  * Sort an array in ascending order, and optionally remove duplicated values.
+  */
+ static ArrayType *
+ array_sort_internal(ArrayType *array, bool unique, void **fn_extra)
+ {
+ 	TypeCacheEntry *type;
+ 	Datum		   *values;
+ 	bool		   *nulls;
+ 	int				nitems,
+ 					nonnulls;
+ 	int				ndims = ARR_NDIM(array);
+ 	int				lbs;
+ 	Oid				element_type = ARR_ELEMTYPE(array);
+ 
+ 	/* TODO: Currently we do not support multi-dimensional arrays. */
+ 	if (ndims > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("cannot use multi-dimensional arrays")));
+ 
+ 	/* Save the lower bound. */
+ 	lbs = (ndims > 0 ? ARR_LBOUND(array)[0] : 1);
+ 
+ 	type = get_type_cache(element_type, fn_extra);
+ 	deconstruct_and_sort(array, &values, &nulls, &nitems, &nonnulls, type);
+ 	if (unique)
+ 		unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 
+ 	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)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = array_sort_internal(array, false, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_unique :
+  *		Remove duplicated elements in an array, and eventually sorted.
+  */
+ Datum
+ array_unique(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *result;
+ 
+ 	result = array_sort_internal(array, true, &fcinfo->flinfo->fn_extra);
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_ARRAYTYPE_P(result);
+ }
+ 
+ /*
+  * array_is_unique :
+  *		Return true iff an array has not duplicated values.
+  *		Note that only one null is allowed in an unique array.
+  */
+ Datum
+ array_is_unique(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *array = PG_GETARG_ARRAYTYPE_P(0);
+ 	bool		result;
+ 	Datum	   *values;
+ 	int			nitems,
+ 				nonnulls;
+ 	int			i;
+ 	TypeCacheEntry *type;
+ 	
+ 	type = get_type_cache(ARR_ELEMTYPE(array), &fcinfo->flinfo->fn_extra);
+ 	deconstruct_and_sort(array, &values, NULL, &nitems, &nonnulls, type);
+ 	if (nitems > nonnulls + 1)
+ 	{
+ 		/* only one null is allowd */
+ 		result = false;
+ 	}
+ 	else
+ 	{
+ 		result = true;
+ 		/* compare for each adjacent */
+ 		for (i = 1; i < nonnulls; i++)
+ 		{
+ 			if (DatumGetBool(FunctionCall2(
+ 				&type->eq_opr_finfo, values[i - 1], values[i])))
+ 			{
+ 				result = false;
+ 				break;
+ 			}
+ 		}
+ 	}
+ 
+ 	PG_FREE_IF_COPY(array, 0);
+ 	PG_RETURN_BOOL(result);
+ }
+ 
+ /*
+  * array_union :
+  *		Concatinate two arrays, and remove duplicated values.
+  */
+ Datum
+ array_union(PG_FUNCTION_ARGS)
+ {
+ 	ArrayType  *v1,
+ 			   *v2;
+ 	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();
+ 
+ 	/* Concatenating a null array is a no-op, just return the other input */
+ 	if (PG_ARGISNULL(0))
+ 	{
+ 		v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 		result = array_sort_internal(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 = array_sort_internal(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);
+ 
+ 	/* TODO: Currently we do not support multi-dimensional arrays. */
+ 	if (ARR_NDIM(v1) > 1 || ARR_NDIM(v2) > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("cannot use multi-dimensional arrays")));
+ 
+ 	element_type1 = ARR_ELEMTYPE(v1);
+ 	element_type2 = ARR_ELEMTYPE(v2);
+ 
+ 	check_concatenable(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);
+ 
+ 	sort_elements(type, values, nulls, nitems, &nonnulls);
+ 	unique_elements(values, nulls, &nitems, &nonnulls, type);
+ 	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);
+ }
+ 
+ /*
+  * array_union_all :
+  *		An alias for array_cat for naming consistency.
+  */
+ Datum
+ array_union_all(PG_FUNCTION_ARGS)
+ {
+ 	return array_cat(fcinfo);
+ }
+ 
+ /*
+  * 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;
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0;
+ 		 n1 < nitems1 && !nulls1[n1] &&
+ 		 n2 < nitems2 && !nulls2[n2];)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		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;
+ }
+ 
+ /*
+  * A worker for array_intersect and array_intersect_all.
+  */
+ static Datum
+ array_intersect_internal(PG_FUNCTION_ARGS, bool all)
+ {
+ 	ArrayType  *v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 	ArrayType  *v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 
+ 	ArrayType  *result;
+ 	Oid			element_type = ARR_ELEMTYPE(v1);
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls1,
+ 			   *nulls2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	check_comparable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	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);
+ }
+ 
+ /*
+  * array_intersect :
+  *		Intersection of two arrays and remove duplicated values.
+  */
+ Datum
+ array_intersect(PG_FUNCTION_ARGS)
+ {
+ 	return array_intersect_internal(fcinfo, false);
+ }
+ 
+ /*
+  * array_intersect_all :
+  *		Intersection of two arrays.
+  */
+ Datum
+ array_intersect_all(PG_FUNCTION_ARGS)
+ {
+ 	return array_intersect_internal(fcinfo, true);
+ }
+ 
+ /*
+  * A worker for array_except and array_except_all.
+  */
+ static Datum
+ array_except_internal(PG_FUNCTION_ARGS, bool all)
+ {
+ 	ArrayType  *v1;
+ 	ArrayType  *v2;
+ 	ArrayType  *result;
+ 	Oid			element_type;
+ 	Datum	   *values1,
+ 			   *values2;
+ 	bool	   *nulls1,
+ 			   *nulls2;
+ 	int			nitems1,
+ 				nitems2,
+ 				nonnulls1,
+ 				nonnulls2;
+ 	int			n1,
+ 				n2,
+ 				n;
+ 	int			lbs = 1;
+ 	TypeCacheEntry *type;
+ 
+ 	if (PG_ARGISNULL(0))
+ 		PG_RETURN_NULL();
+ 
+ 	v1 = PG_GETARG_ARRAYTYPE_P(0);
+ 
+ 	/* fast path for except null */
+ 	if (PG_ARGISNULL(1))
+ 	{
+ 		result = array_sort_internal(v1, !all, &fcinfo->flinfo->fn_extra);
+ 		PG_FREE_IF_COPY(v1, 0);
+ 		PG_RETURN_ARRAYTYPE_P(result);
+ 	}
+ 
+ 	v2 = PG_GETARG_ARRAYTYPE_P(1);
+ 	element_type = ARR_ELEMTYPE(v1);
+ 	check_concatenable(element_type, ARR_ELEMTYPE(v2));
+ 	type = get_type_cache(element_type, &fcinfo->flinfo->fn_extra);
+ 
+ 	deconstruct_and_sort(v1, &values1, &nulls1, &nitems1, &nonnulls1, type);
+ 	if (!all)
+ 		unique_elements(values1, nulls1, &nitems1, &nonnulls1, type);
+ 	deconstruct_and_sort(v2, &values2, &nulls2, &nitems2, &nonnulls2, type);
+ 	if (!all)
+ 		unique_elements(values2, nulls2, &nitems2, &nonnulls2, type);
+ 
+ 	/* add non-nulls */
+ 	for (n = n1 = n2 = 0; n1 < nonnulls1 && n2 < nonnulls2;)
+ 	{
+ 		int		r = DatumGetInt32(FunctionCall2(&type->cmp_proc_finfo,
+ 												values1[n1], values2[n2]));
+ 		if (r < 0)
+ 			values1[n++] = values1[n1++];
+ 		else
+ 			n2++;
+ 		if (r == 0)
+ 			n1++;
+ 	}
+ 	for (; n1 < nonnulls1; n1++, n++)
+ 		values1[n] = values1[n1];
+ 
+ 	/* add nulls */
+ 	for (n1 = nonnulls1; n1 < nitems1 - (nitems2 - nonnulls2); 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);
+ }
+ 
+ /*
+  * array_except :
+  *		Subtraction of two arrays and remove duplicated values.
+  */
+ Datum
+ array_except(PG_FUNCTION_ARGS)
+ {
+ 	return array_except_internal(fcinfo, false);
+ }
+ 
+ /*
+  * array_except_all :
+  *		Subtraction of two arrays.
+  */
+ Datum
+ array_except_all(PG_FUNCTION_ARGS)
+ {
+ 	return array_except_internal(fcinfo, true);
+ }
+ 
+ /*
+  * array_fusion :
+  *		Similar to array_agg, but the input values are arrays.
+  */
+ Datum
+ array_fusion_transfn(PG_FUNCTION_ARGS)
+ {
+ 	MemoryContext		aggcontext;
+ 	ArrayBuildState	   *state;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "array_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;
+ 
+ 		if (ARR_NDIM(v) > 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 					 errmsg("cannot use multi-dimensional arrays")));
+ 
+ 		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);
+ }
+ 
+ /*
+  * array_intersection :
+  *		Intersection of all input arrays.
+  */
+ typedef struct ArrayIntersectionState
+ {
+ 	Oid		element_type;
+ 	int		nitems;
+ 	Datum  *values;
+ 	bool   *nulls;
+ } ArrayIntersectionState;
+ 
+ Datum
+ array_intersection_transfn(PG_FUNCTION_ARGS)
+ {
+ 	MemoryContext			aggcontext;
+ 	ArrayIntersectionState *state;
+ 
+ 	if (!AggCheckCallContext(fcinfo, &aggcontext))
+ 	{
+ 		/* cannot be called directly because of internal-type argument */
+ 		elog(ERROR, "array_intersection_transfn called in non-aggregate context");
+ 	}
+ 
+ 	state = PG_ARGISNULL(0) ? NULL
+ 							: (ArrayIntersectionState *) PG_GETARG_POINTER(0);
+ 	if (!PG_ARGISNULL(1))
+ 	{
+ 		ArrayType	   *v = PG_GETARG_ARRAYTYPE_P(1);
+ 		TypeCacheEntry *type;
+ 
+ 		type = get_type_cache(ARR_ELEMTYPE(v), &fcinfo->flinfo->fn_extra);
+ 
+ 		if (state == NULL)
+ 		{
+ 			MemoryContext	oldcontext;
+ 
+ 			oldcontext = MemoryContextSwitchTo(aggcontext);
+ 			state = (ArrayIntersectionState *)
+ 						palloc(sizeof(ArrayIntersectionState));
+ 			state->element_type = ARR_ELEMTYPE(v);
+ 			deconstruct_and_sort(v, &state->values, &state->nulls,
+ 								 &state->nitems, NULL, type);
+ 			MemoryContextSwitchTo(oldcontext);
+ 		}
+ 		else
+ 		{
+ 			Datum	   *values;
+ 			bool	   *nulls;
+ 			int			nitems;
+ 
+ 			check_concatenable(state->element_type, ARR_ELEMTYPE(v));
+ 			deconstruct_and_sort(v, &values, &nulls, &nitems, NULL, type);
+ 			state->nitems = intersect_sorted_arrays(type,
+ 								state->values, state->nulls, state->nitems,
+ 								values, nulls, nitems);
+ 		}
+ 
+ 		PG_FREE_IF_COPY(v, 1);
+ 	}
+ 
+ 	PG_RETURN_POINTER(state);
+ }
+ 
+ Datum
+ array_intersection_finalfn(PG_FUNCTION_ARGS)
+ {
+ 	ArrayIntersectionState *state;
+ 	ArrayType		   *result;
+ 	int					lbs = 1;
+ 	TypeCacheEntry	   *type;
+ 
+ 	state = PG_ARGISNULL(0) ? NULL
+ 							: (ArrayIntersectionState *) 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);
+ }
+ 
+ static void
+ check_concatenable(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 26966d2..83d8312 100644
*** a/src/include/catalog/pg_aggregate.h
--- b/src/include/catalog/pg_aggregate.h
*************** DATA(insert ( 2901 xmlconcat2	  -					0	
*** 222,227 ****
--- 222,229 ----
  
  /* array */
  DATA(insert ( 2335	array_agg_transfn	array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3160	array_fusion_transfn array_agg_finalfn		0	2281	_null_ ));
+ DATA(insert ( 3163	array_intersection_transfn array_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 a859885..cb619bf 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2334 (  array_agg_fina
*** 1062,1067 ****
--- 1062,1101 ----
  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 = 3147 (  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 = 3148 (  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 at end of array");
+ DATA(insert OID = 3149 (  array_trim	   PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 23" _null_ _null_ _null_ _null_ array_trim _null_ _null_ _null_ ));
+ DESCR("remove elements at end of array");
+ DATA(insert OID = 3150 (  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 = 3151 (  array_unique	   PGNSP PGUID 12 1 0 0 f f f t f i 1 0 2277 "2277" _null_ _null_ _null_ _null_ array_unique _null_ _null_ _null_ ));
+ DESCR("remove duplicated values in an array");
+ DATA(insert OID = 3152 (  array_is_unique  PGNSP PGUID 12 1 0 0 f f f t f i 1 0 16 "2277" _null_ _null_ _null_ _null_ array_is_unique _null_ _null_ _null_ ));
+ DESCR("no duplicated elements?");
+ DATA(insert OID = 3153 (  array_union	   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_union _null_ _null_ _null_ ));
+ DESCR("concatenate two arrays and remove duplicated values");
+ DATA(insert OID = 3154 (  array_union_all  PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_union_all _null_ _null_ _null_ ));
+ DESCR("concatenate two arrays");
+ DATA(insert OID = 3155 (  array_intersect  PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_intersect _null_ _null_ _null_ ));
+ DESCR("intersection of two arrays and remove duplicated values");
+ DATA(insert OID = 3156 (  array_intersect_all PGNSP PGUID 12 1 0 0 f f f t f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_intersect_all _null_ _null_ _null_ ));
+ DESCR("intersection of two arrays");
+ DATA(insert OID = 3157 (  array_except	   PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_except _null_ _null_ _null_ ));
+ DESCR("subtraction of two arrays and remove duplicated values");
+ DATA(insert OID = 3158 (  array_except_all PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2277 "2277 2277" _null_ _null_ _null_ _null_ array_except_all _null_ _null_ _null_ ));
+ DESCR("subtraction of two arrays");
+ DATA(insert OID = 3159 (  array_fusion_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_fusion_transfn _null_ _null_ _null_ ));
+ DESCR("fusion transition function");
+ DATA(insert OID = 3160 (  array_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 arrays into an array");
+ DATA(insert OID = 3161 (  array_intersection_transfn PGNSP PGUID 12 1 0 0 f f f f f i 2 0 2281 "2281 2277" _null_ _null_ _null_ _null_ array_intersection_transfn _null_ _null_ _null_ ));
+ DESCR("intersection transition function");
+ DATA(insert OID = 3162 (  array_intersection_finalfn PGNSP PGUID 12 1 0 0 f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_intersection_finalfn _null_ _null_ _null_ ));
+ DESCR("intersection final function");
+ DATA(insert OID = 3163 (  array_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 8b7db79..cf7de13 100644
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
*************** extern TypeName *makeTypeNameFromOid(Oid
*** 72,77 ****
--- 72,78 ----
  
  extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype,
  			 List *args, Oid collid, 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/utils/array.h b/src/include/utils/array.h
index 7f7e744..725cc38 100644
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
*************** extern ArrayType *create_singleton_array
*** 281,285 ****
--- 281,300 ----
  
  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_trim(PG_FUNCTION_ARGS);
+ extern Datum array_sort(PG_FUNCTION_ARGS);
+ extern Datum array_unique(PG_FUNCTION_ARGS);
+ extern Datum array_is_unique(PG_FUNCTION_ARGS);
+ extern Datum array_union(PG_FUNCTION_ARGS);
+ extern Datum array_union_all(PG_FUNCTION_ARGS);
+ extern Datum array_intersect(PG_FUNCTION_ARGS);
+ extern Datum array_intersect_all(PG_FUNCTION_ARGS);
+ extern Datum array_except(PG_FUNCTION_ARGS);
+ extern Datum array_except_all(PG_FUNCTION_ARGS);
+ extern Datum array_fusion_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_intersection_transfn(PG_FUNCTION_ARGS);
+ extern Datum array_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 7b05ce3..334121b 100644
*** a/src/test/regress/expected/arrays.out
--- b/src/test/regress/expected/arrays.out
*************** select * from t1;
*** 1558,1560 ****
--- 1558,1713 ----
   [5:5]={"(42,43)"}
  (1 row)
  
+ -- sort, unique, and set operations
+ SELECT cardinality(ARRAY[1, 2, 3]),
+ 	   cardinality(ARRAY[[1, 2], [3, 4]]);
+  cardinality | cardinality 
+ -------------+-------------
+            3 |           4
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   array_trim('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2),
+ 	   array_trim(ARRAY[[1, 2], [3, 4]], 1);
+  trim_array | trim_array |     array_trim     | array_trim 
+ ------------+------------+--------------------+------------
+  {1,2,3}    | {1}        | [-2:0]={-2,NULL,0} | {{1,2}}
+ (1 row)
+ 
+ SELECT trim_array(ARRAY[1, 2, 3], -1); -- ERROR
+ ERROR:  number of trimmed elements (-1) must not be negative
+ SELECT trim_array(ARRAY[1, 2, 3], 4); -- ERROR
+ ERROR:  number of trimmed elements (4) is greater than number of elements (3)
+ SELECT array_is_unique(ARRAY[1, 2, 3]);
+  array_is_unique 
+ -----------------
+  t
+ (1 row)
+ 
+ SELECT array_is_unique(ARRAY['A', 'A', 'B']);
+  array_is_unique 
+ -----------------
+  f
+ (1 row)
+ 
+ SELECT array_is_unique(ARRAY[1, NULL]),
+ 	   array_is_unique(ARRAY[1, NULL, NULL]);
+  array_is_unique | array_is_unique 
+ -----------------+-----------------
+  t               | f
+ (1 row)
+ 
+ SELECT array_is_unique(ARRAY[[1]]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+      array_union_all      
+ --------------------------
+  {2,NULL,1,2,NULL,2,NULL}
+ (1 row)
+ 
+ SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+   array_union_all  
+ -------------------
+  {2,NULL,1,2,NULL}
+ (1 row)
+ 
+ SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+  array_union 
+ -------------
+  {1,2,NULL}
+ (1 row)
+ 
+ SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], NULL);
+  array_union 
+ -------------
+  {1,2,NULL}
+ (1 row)
+ 
+ SELECT array_union(ARRAY[[1]], ARRAY[1]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+  array_intersect_all 
+ ---------------------
+  {2,NULL}
+ (1 row)
+ 
+ SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+  array_intersect_all 
+ ---------------------
+  
+ (1 row)
+ 
+ SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+  array_intersect 
+ -----------------
+  {2,NULL}
+ (1 row)
+ 
+ SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], NULL);
+  array_intersect 
+ -----------------
+  
+ (1 row)
+ 
+ SELECT array_intersect(ARRAY[[1]], ARRAY[1]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+  array_except_all 
+ ------------------
+  {1,2,NULL}
+ (1 row)
+ 
+ SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+  array_except_all  
+ -------------------
+  {1,2,2,NULL,NULL}
+ (1 row)
+ 
+ SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+  array_except 
+ --------------
+  {1}
+ (1 row)
+ 
+ SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], NULL);
+  array_except 
+ --------------
+  {1,2,NULL}
+ (1 row)
+ 
+ SELECT array_except(ARRAY[[1]], ARRAY[1]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]),
+ 	   array_sort(ARRAY['A', 'C', 'B', 'C', 'C', 'A']);
+       array_sort       |  array_sort   
+ -----------------------+---------------
+  {1,1,2,3,3,NULL,NULL} | {A,A,B,C,C,C}
+ (1 row)
+ 
+ SELECT array_sort(ARRAY[[1]]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_unique(ARRAY[1, 3, 2, 3, NULL, 1, NULL]),
+ 	   array_unique(ARRAY['A', 'C', 'B', 'C', 'C', 'A']);
+  array_unique | array_unique 
+ --------------+--------------
+  {1,2,3,NULL} | {A,B,C}
+ (1 row)
+ 
+ SELECT array_unique(ARRAY[[1]]); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_fusion(a), array_intersection(a)
+ 	FROM (VALUES
+ 		(ARRAY[1, 2, 3, 2, 2]),
+ 		(ARRAY[1, 2, 4, 2]),
+ 		(ARRAY[NULL, 3, 2, 1])
+ 	) AS t(a);
+           array_fusion          | array_intersection 
+ --------------------------------+--------------------
+  {1,2,3,2,2,1,2,4,2,NULL,3,2,1} | {1,2}
+ (1 row)
+ 
+ SELECT array_fusion(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
+ SELECT array_intersection(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR
+ ERROR:  cannot use multi-dimensional arrays
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 9ea53b1..90d7aaa 100644
*** a/src/test/regress/sql/arrays.sql
--- b/src/test/regress/sql/arrays.sql
*************** insert into t1 (f1[5].q1) values(42);
*** 438,440 ****
--- 438,485 ----
  select * from t1;
  update t1 set f1[5].q2 = 43;
  select * from t1;
+ 
+ -- sort, unique, and set operations
+ 
+ SELECT cardinality(ARRAY[1, 2, 3]),
+ 	   cardinality(ARRAY[[1, 2], [3, 4]]);
+ SELECT trim_array(ARRAY[1, 2, 3], 0),
+        trim_array(ARRAY[1, 2, 3], 2),
+ 	   array_trim('[-2:2]={-2,NULL,0,NULL,2}'::int[], 2),
+ 	   array_trim(ARRAY[[1, 2], [3, 4]], 1);
+ SELECT trim_array(ARRAY[1, 2, 3], -1); -- ERROR
+ SELECT trim_array(ARRAY[1, 2, 3], 4); -- ERROR
+ SELECT array_is_unique(ARRAY[1, 2, 3]);
+ SELECT array_is_unique(ARRAY['A', 'A', 'B']);
+ SELECT array_is_unique(ARRAY[1, NULL]),
+ 	   array_is_unique(ARRAY[1, NULL, NULL]);
+ SELECT array_is_unique(ARRAY[[1]]); -- ERROR
+ SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_union_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_union(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_union(ARRAY[[1]], ARRAY[1]); -- ERROR
+ SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_intersect_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_intersect(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_intersect(ARRAY[[1]], ARRAY[1]); -- ERROR
+ SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_except_all(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], ARRAY[2, NULL]);
+ SELECT array_except(ARRAY[2, NULL, 1, 2, NULL], NULL);
+ SELECT array_except(ARRAY[[1]], ARRAY[1]); -- ERROR
+ SELECT array_sort(ARRAY[1, 3, 2, 3, NULL, 1, NULL]),
+ 	   array_sort(ARRAY['A', 'C', 'B', 'C', 'C', 'A']);
+ SELECT array_sort(ARRAY[[1]]); -- ERROR
+ SELECT array_unique(ARRAY[1, 3, 2, 3, NULL, 1, NULL]),
+ 	   array_unique(ARRAY['A', 'C', 'B', 'C', 'C', 'A']);
+ SELECT array_unique(ARRAY[[1]]); -- ERROR
+ SELECT array_fusion(a), array_intersection(a)
+ 	FROM (VALUES
+ 		(ARRAY[1, 2, 3, 2, 2]),
+ 		(ARRAY[1, 2, 4, 2]),
+ 		(ARRAY[NULL, 3, 2, 1])
+ 	) AS t(a);
+ SELECT array_fusion(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR
+ SELECT array_intersection(a) FROM (VALUES (ARRAY[[1]])) AS t(a); -- ERROR
#39Robert Haas
robertmhaas@gmail.com
In reply to: Itagaki Takahiro (#38)
Re: multiset patch review

On Tue, Feb 15, 2011 at 4:31 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

array_flatten() no longer exists. I added array_trim() as an alias
to trim_array() because it would be a FAQ.

I don't like the alias thing - let's add one name or the other, not both.

Similarly, let's NOT add array_union_all as an alias for array_concat.

'cannot use multi-dimensional arrays' reads awkwardly to me. I think
it should say something like "sorting of multi-dimensional arrays is
not supported".

multi-demensional -> multi-dimensional

slaces -> slices

The formula in the trim_array comment is apparently misparenthesized.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#40Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#39)
Re: multiset patch review

On Tue, Feb 15, 2011 at 7:13 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Tue, Feb 15, 2011 at 4:31 AM, Itagaki Takahiro
<itagaki.takahiro@gmail.com> wrote:

array_flatten() no longer exists. I added array_trim() as an alias
to trim_array() because it would be a FAQ.

I don't like the alias thing - let's add one name or the other, not both.

Similarly, let's NOT add array_union_all as an alias for array_concat.

'cannot use multi-dimensional arrays' reads awkwardly to me.  I think
it should say something like "sorting of multi-dimensional arrays is
not supported".

multi-demensional -> multi-dimensional

slaces -> slices

The formula in the trim_array comment is apparently misparenthesized.

I think we're out of time to keep bikeshedding this. Let's revisit it for 9.2.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company