This patch adds basic support of arrays in foreign keys, by allowing to define a referencing column as an array of elements having the same type as the referenced column in the referenced table. Every NOT NULL element in the referencing array is matched against the referenced table.

Example:

CREATE TABLE pt (
  id INTEGER PRIMARY KEY,
  ...
);

CREATE TABLE ft (
  id SERIAL PRIMARY KEY,
  pids INTEGER[] REFERENCES pt,
  ...
);


This patch is for discussion and has been built against HEAD.
It compiles and passes all regressions tests (including specific ones - see the src/test/regress/sql/foreign_key.sql file). Empty arrays, multi-dimensional arrays, duplicate elements and NULL values are allowed.

We had to enforce some limitations, due to the lack (yet) of a clear and universally accepted behaviour and strategy. For example, consider the ON DELETE action on the above tables: in case of delete of a record in the 'pt' table, should we remove the whole row or just the values from the array?
We hope we can start a discussion from here.

Current limitations:

* Only arrays of the same type as the primary key in the referenced table are supported
* multi-column foreign keys are not supported (only single column)
* Only RESTRICT and NO ACTION methods for referential integrity enforcement are currently supported

TODO:
* Improve check for empty arrays, which might interfere with SSI (see below)
* Verify interaction with serializable transactions

AUTHORS:
* Gabriele Bartolini <gabriele.bartol...@2ndquadrant.it>
* Marco Nenciarini <marco.nenciar...@2ndquadrant.it>

Cheers,
Gabriele (and Marco)

--
 Gabriele Bartolini - 2ndQuadrant Italia
 PostgreSQL Training, Services and Support
 gabriele.bartol...@2ndquadrant.it | www.2ndQuadrant.it

*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
***************
*** 764,769 **** CREATE TABLE order_items (
--- 764,796 ----
      the last table.
     </para>
  
+    <para>
+     Another option you have with foreign keys is to use a referencing column
+     which is an array of elements with the same type as the referenced column
+     in the related table. This feature, also known as <firstterm>foreign key 
arrays</firstterm>,
+     is described in the following example:
+ 
+ <programlisting>
+ CREATE TABLE countries (
+     country_id integer PRIMARY KEY,
+     name text,
+     ...
+ );
+ 
+ CREATE TABLE people (
+     person_id integer PRIMARY KEY,
+     first_name text,
+     last_name text,
+     ...
+     citizenship_ids integer[] REFERENCES countries
+ );
+ </programlisting>    
+ 
+     The above example lists in an array the citizenships held by
+     a person and enforces referential integrity checks.
+     
+    </para>
+ 
     <indexterm>
      <primary>CASCADE</primary>
      <secondary>foreign key action</secondary>
***************
*** 852,857 **** CREATE TABLE order_items (
--- 879,891 ----
     </para>
  
     <para>
+     When working with foreign key arrays, you are currently limited
+     to <literal>RESTRICT</literal> and <literal>NO ACTION</literal>
+     options, as the default behaviour for the other cases is not
+     clearly and universally determined yet.
+    </para>
+ 
+    <para>
      More information about updating and deleting data is in <xref
      linkend="dml">.
     </para>
*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 576,581 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] 
TABLE [ IF NOT EXI
--- 576,587 ----
       </para>
  
       <para>
+       If the referencing column is an array of elements of the same type as
+       the referenced column in the referenced table, the value of each element
+       of the array will be matched against some row of the referenced table.
+      </para>
+ 
+      <para>
        A value inserted into the referencing column(s) is matched against the
        values of the referenced table and referenced columns using the
        given match type.  There are three match types: <literal>MATCH
***************
*** 634,640 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] 
TABLE [ IF NOT EXI
           <para>
            Delete any rows referencing the deleted row, or update the
            value of the referencing column to the new value of the
!           referenced column, respectively.
           </para>
          </listitem>
         </varlistentry>
--- 640,647 ----
           <para>
            Delete any rows referencing the deleted row, or update the
            value of the referencing column to the new value of the
!           referenced column, respectively. Foreign key arrays are not
!           supported by this action (as the behaviour is not easily 
determined).
           </para>
          </listitem>
         </varlistentry>
***************
*** 643,649 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] 
TABLE [ IF NOT EXI
          <term><literal>SET NULL</literal></term>
          <listitem>
           <para>
!           Set the referencing column(s) to null.
           </para>
          </listitem>
         </varlistentry>
--- 650,657 ----
          <term><literal>SET NULL</literal></term>
          <listitem>
           <para>
!           Set the referencing column(s) to null. Foreign key arrays are not
!           supported by this action (as the behaviour is not easily 
determined).
           </para>
          </listitem>
         </varlistentry>
***************
*** 652,658 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] 
TABLE [ IF NOT EXI
          <term><literal>SET DEFAULT</literal></term>
          <listitem>
           <para>
!           Set the referencing column(s) to their default values.
           </para>
          </listitem>
         </varlistentry>
--- 660,667 ----
          <term><literal>SET DEFAULT</literal></term>
          <listitem>
           <para>
!           Set the referencing column(s) to their default values. Foreign key 
arrays are not
!           supported by this action (as the behaviour is not easily 
determined).
           </para>
          </listitem>
         </varlistentry>
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 5705,5710 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation 
rel,
--- 5705,5735 ----
                Oid                     ffeqop;
                int16           eqstrategy;
  
+               /* Check if foreign key is an array of primary key types */
+               const bool              is_foreign_key_array = (fktype == 
get_array_type (pktype));
+ 
+               /* Enforce foreign key array restrictions */
+               if (is_foreign_key_array)
+               {
+                       /*
+                        * Foreign key array must not be part of a multi-column 
foreign key
+                        */
+                       if (is_foreign_key_array && numpks > 1)
+                               ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+                                       errmsg("foreign key arrays must not be 
part of a multi-column foreign key")));
+ 
+                       /*
+                        * We have to restrict foreign key array to NO ACTION 
and RESTRICT mode
+                        * until the behaviour triggered by the other actions 
is clearer and well defined
+                        */
+                       if ((fkconstraint->fk_upd_action != 
FKCONSTR_ACTION_NOACTION && fkconstraint->fk_upd_action != 
FKCONSTR_ACTION_RESTRICT)
+                               || (fkconstraint->fk_del_action != 
FKCONSTR_ACTION_NOACTION && fkconstraint->fk_del_action != 
FKCONSTR_ACTION_RESTRICT))
+                               ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+                                       errmsg("NO ACTION and RESTRICT are the 
only supported actions for foreign key arrays")));
+               }
+ 
                /* We need several fields out of the pg_opclass entry */
                cla_ht = SearchSysCache1(CLAOID, 
ObjectIdGetDatum(opclasses[i]));
                if (!HeapTupleIsValid(cla_ht))
***************
*** 5766,5772 **** ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation 
rel,
                        Oid                     target_typeids[2];
  
                        input_typeids[0] = pktype;
!                       input_typeids[1] = fktype;
                        target_typeids[0] = opcintype;
                        target_typeids[1] = opcintype;
                        if (can_coerce_type(2, input_typeids, target_typeids,
--- 5791,5801 ----
                        Oid                     target_typeids[2];
  
                        input_typeids[0] = pktype;
!                       /* When is FKA we must use for FK the same type of PK */
!                       if (is_foreign_key_array)
!                               input_typeids[1] = pktype;
!                       else
!                               input_typeids[1] = fktype;
                        target_typeids[0] = opcintype;
                        target_typeids[1] = opcintype;
                        if (can_coerce_type(2, input_typeids, target_typeids,
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 460,465 **** RI_FKey_check(PG_FUNCTION_ARGS)
--- 460,466 ----
                char            paramname[16];
                const char *querysep;
                Oid                     queryoids[RI_MAX_NUMKEYS];
+               bool            is_foreign_key_array = false;
  
                /* ----------
                 * The query string built is
***************
*** 476,493 **** RI_FKey_check(PG_FUNCTION_ARGS)
                {
                        Oid                     pk_type = RIAttType(pk_rel, 
riinfo.pk_attnums[i]);
                        Oid                     fk_type = RIAttType(fk_rel, 
riinfo.fk_attnums[i]);
  
                        quoteOneName(attname,
                                                 RIAttName(pk_rel, 
riinfo.pk_attnums[i]));
                        sprintf(paramname, "$%d", i + 1);
!                       ri_GenerateQual(&querybuf, querysep,
!                                                       attname, pk_type,
!                                                       riinfo.pf_eq_oprs[i],
!                                                       paramname, fk_type);
                        querysep = "AND";
                        queryoids[i] = fk_type;
                }
!               appendStringInfo(&querybuf, " FOR SHARE OF x");
  
                /* Prepare and save the plan */
                qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
--- 477,524 ----
                {
                        Oid                     pk_type = RIAttType(pk_rel, 
riinfo.pk_attnums[i]);
                        Oid                     fk_type = RIAttType(fk_rel, 
riinfo.fk_attnums[i]);
+                       is_foreign_key_array = (fk_type == get_array_type 
(pk_type));
  
                        quoteOneName(attname,
                                                 RIAttName(pk_rel, 
riinfo.pk_attnums[i]));
                        sprintf(paramname, "$%d", i + 1);
!                       /*
!                        * In case of an array foreign key, we check that every
!                        * DISTINCT NOT NULL value in the array is present in 
the PK table.
!                        * XXX: This works because the query is executed with 
LIMIT 1,
!                        * but may not work properly with SSI (a better 
approach would be
!                        * to inspect the array and skip the check in case of 
empty arrays).
!                        */
!                       if (is_foreign_key_array)
!                       {
!                               appendStringInfo(&querybuf, " %s (SELECT 
count(*) FROM (SELECT DISTINCT UNNEST(%s)) y WHERE y IS NOT NULL)", querysep, 
paramname);
!                               appendStringInfo(&querybuf, " = (SELECT 
count(*) FROM (SELECT 1 FROM ONLY %s y",  pkrelname);
!                               ri_GenerateQual(&querybuf, "WHERE",
!                                                               attname, 
pk_type,
!                                                               
riinfo.pf_eq_oprs[i],
!                                                               paramname, 
fk_type);
!                               /*
!                                * We lock for share every row in the 
pkreltable that is
!                                * referenced by the array elements
!                                */
!                               appendStringInfo(&querybuf, " FOR SHARE OF y) 
z)");
!                       }
!                       else
!                       {
!                               ri_GenerateQual(&querybuf, querysep,
!                                                               attname, 
pk_type,
!                                                               
riinfo.pf_eq_oprs[i],
!                                                               paramname, 
fk_type);
!                       }
                        querysep = "AND";
                        queryoids[i] = fk_type;
                }
!               /*
!                * We skip locking for share in case of foreign key arrays
!                * as it has been done in the inner subquery
!                */
!               if (! is_foreign_key_array)
!                       appendStringInfo(&querybuf, " FOR SHARE OF x");
  
                /* Prepare and save the plan */
                qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
***************
*** 2949,2957 **** ri_GenerateQual(StringInfo buf,
                ri_add_cast_to(buf, operform->oprleft);
        appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
        appendStringInfoString(buf, oprname);
!       appendStringInfo(buf, ") %s", rightop);
!       if (rightoptype != operform->oprright)
!               ri_add_cast_to(buf, operform->oprright);
  
        ReleaseSysCache(opertup);
  }
--- 2980,3002 ----
                ri_add_cast_to(buf, operform->oprleft);
        appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
        appendStringInfoString(buf, oprname);
!       /*
!        * If rightoptype is an array of leftoptype check equality using ANY().
!        * Needed for array support in foreign keys.
!        */
!       if (rightoptype == get_array_type (leftoptype))
!       {
!               appendStringInfo(buf, ") ANY (%s", rightop);
!               if (rightoptype != get_array_type (operform->oprright))
!                       ri_add_cast_to(buf, get_array_type 
(operform->oprright));
!               appendStringInfo(buf, ")");
!       }
!       else
!       {
!               appendStringInfo(buf, ") %s", rightop);
!               if (rightoptype != operform->oprright)
!                       ri_add_cast_to(buf, operform->oprright);
!       }
  
        ReleaseSysCache(opertup);
  }
*** a/src/test/regress/expected/foreign_key.out
--- b/src/test/regress/expected/foreign_key.out
***************
*** 968,978 **** drop table pktable;
  drop table pktable_base;
  -- 2 columns (1 table), mismatched types
  create table pktable_base(base1 int not null, base2 int);
- create table pktable(ptest1 inet, ptest2 inet[], primary key(base1, ptest1), 
foreign key(base2, ptest2) references
-                                              pktable(base1, ptest1)) inherits 
(pktable_base);
- NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" 
for table "pktable"
- ERROR:  foreign key constraint "pktable_base2_fkey" cannot be implemented
- DETAIL:  Key columns "ptest2" and "ptest1" are of incompatible types: inet[] 
and inet.
  create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), 
foreign key(base2, ptest2) references
                                               pktable(ptest1, base1)) inherits 
(pktable_base);
  NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" 
for table "pktable"
--- 968,973 ----
***************
*** 1319,1321 **** begin;
--- 1314,1537 ----
  (2 rows)
  
  commit;
+ -- ARRAY FK
+ --
+ CREATE TABLE PKTABLEFORARRAY ( ptest1 int PRIMARY KEY, ptest2 text );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"pktableforarray_pkey" for table "pktableforarray"
+ -- Insert test data into PKTABLEFORARRAY
+ INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test1');
+ INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test2');
+ INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test3');
+ INSERT INTO PKTABLEFORARRAY VALUES (4, 'Test4');
+ INSERT INTO PKTABLEFORARRAY VALUES (5, 'Test5');
+ -- Check alter table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+ ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ftest1) 
REFERENCES PKTABLEFORARRAY;
+ DROP TABLE FKTABLEFORARRAY;
+ -- Check create table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL REFERENCES 
PKTABLEFORARRAY, ftest2 int );
+ -- Insert successful rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 3);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 4);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES (NULL, 1);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{}', 5);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 6);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 7);
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 8);
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 9);
+ -- Insert failed rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 3);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({6}) is not present in table "pktableforarray".
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 4);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({4,6}) is not present in table "pktableforarray".
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 5);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({6,NULL}) is not present in table "pktableforarray".
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 6);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({6,NULL,4,NULL}) is not present in table 
"pktableforarray".
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 7);
+ ERROR:  insert or update on table "fktableforarraymdim" violates foreign key 
constraint "fktableforarraymdim_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({{1,2},{6,NULL}}) is not present in table 
"pktableforarray".
+ INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 1);
+ ERROR:  null value in column "ftest1" violates not-null constraint
+ -- Check FKTABLE
+ SELECT * FROM FKTABLEFORARRAY;
+   ftest1  | ftest2 
+ ----------+--------
+  {1}      |      2
+  {2}      |      3
+  {1}      |      2
+  {3}      |      4
+  {1}      |      2
+  {4,5}    |      2
+  {4,4}    |      2
+           |      1
+  {}       |      5
+  {1,NULL} |      6
+  {NULL}   |      7
+ (11 rows)
+ 
+ -- Delete a row from PK TABLE (must fail)
+ DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+ ERROR:  update or delete on table "pktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+ DETAIL:  Key (ptest1)=(1) is still referenced from table "fktableforarray".
+ -- Check FKTABLE for removal of matched row
+ SELECT * FROM FKTABLEFORARRAY;
+   ftest1  | ftest2 
+ ----------+--------
+  {1}      |      2
+  {2}      |      3
+  {1}      |      2
+  {3}      |      4
+  {1}      |      2
+  {4,5}    |      2
+  {4,4}    |      2
+           |      1
+  {}       |      5
+  {1,NULL} |      6
+  {NULL}   |      7
+ (11 rows)
+ 
+ -- Update a row from PK TABLE (must fail)
+ UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+ ERROR:  update or delete on table "pktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+ DETAIL:  Key (ptest1)=(1) is still referenced from table "fktableforarray".
+ -- Check FKTABLE for update of matched row
+ SELECT * FROM FKTABLEFORARRAY;
+   ftest1  | ftest2 
+ ----------+--------
+  {1}      |      2
+  {2}      |      3
+  {1}      |      2
+  {3}      |      4
+  {1}      |      2
+  {4,5}    |      2
+  {4,4}    |      2
+           |      1
+  {}       |      5
+  {1,NULL} |      6
+  {NULL}   |      7
+ (11 rows)
+ 
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE FKTABLEFORARRAYNOTNULL;
+ DROP TABLE FKTABLEFORARRAYMDIM;
+ -- Allowed references with actions 'RESTRICT' and 'NO ACTION'
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+ DROP TABLE FKTABLEFORARRAY;
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+ DROP TABLE FKTABLEFORARRAY;
+ -- Failed attempts of creating references with actions other than 'RESTRICT' 
and 'NO ACTION'
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ ERROR:  NO ACTION and RESTRICT are the only supported actions for foreign key 
arrays
+ -- Cleanup
+ DROP TABLE PKTABLEFORARRAY;
+ -- Repeat a similar test using CHAR(1) keys rather than INTEGER
+ CREATE TABLE PKTABLEFORARRAY ( ptest1 CHAR(1) PRIMARY KEY, ptest2 text );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"pktableforarray_pkey" for table "pktableforarray"
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'Test A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('B', 'Test B');
+ INSERT INTO PKTABLEFORARRAY VALUES ('C', 'Test C');
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 char(1)[] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A"}', 1);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"B"}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"C"}', 3);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A","B","C"}', 4);
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"D"}', 5);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({D}) is not present in table "pktableforarray".
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A","B","D"}', 6);
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_ftest1_fkey"
+ DETAIL:  Key (ftest1)=({A,B,D}) is not present in table "pktableforarray".
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
+ -- Composite primary keys (unsupported)
+ CREATE TABLE PKTABLEFORARRAY ( id1 CHAR(1), id2 CHAR(1), ptest2 text, PRIMARY 
KEY (id1, id2) );
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"pktableforarray_pkey" for table "pktableforarray"
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'A', 'Test A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'B', 'Test B');
+ INSERT INTO PKTABLEFORARRAY VALUES ('B', 'C', 'Test B');
+ -- Must fail (cannot use arrays in composite foreign keys - use an array of 
composite types)
+ CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, 
FOREIGN KEY (fid1, fid2) REFERENCES PKTABLEFORARRAY (id1, id2));
+ ERROR:  foreign key arrays must not be part of a multi-column foreign key
+ -- Cleanup
+ DROP TABLE PKTABLEFORARRAY;
+ -- Test foreign key arrays with composite type
+ CREATE TYPE INVOICEID AS (year_part INTEGER, progressive_part INTEGER);
+ CREATE TABLE PKTABLEFORARRAY ( id INVOICEID PRIMARY KEY,  ptest2 text);
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"pktableforarray_pkey" for table "pktableforarray"
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2010, 99), 'Last invoice for 2010');
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2011, 1), 'First invoice for 2011');
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2011, 2), 'Second invoice for 2011');
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( id SERIAL PRIMARY KEY, invoice_ids INVOICEID[] 
REFERENCES PKTABLEFORARRAY, ftest2 TEXT );
+ NOTICE:  CREATE TABLE will create implicit sequence "fktableforarray_id_seq" 
for serial column "fktableforarray.id"
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"fktableforarray_pkey" for table "fktableforarray"
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2010,99)']::INVOICEID[], 'Product A');
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,1)','(2011,2)']::INVOICEID[], 'Product B');
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,2)']::INVOICEID[], 'Product C');
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,99)']::INVOICEID[], 'Product A');
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_invoice_ids_fkey"
+ DETAIL:  Key (invoice_ids)=({"(2011,99)"}) is not present in table 
"pktableforarray".
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,1)','(2010,1)']::INVOICEID[], 'Product B');
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_invoice_ids_fkey"
+ DETAIL:  Key (invoice_ids)=({"(2011,1)","(2010,1)"}) is not present in table 
"pktableforarray".
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
+ -- Create primary table with a primary key array
+ CREATE TABLE PKTABLEFORARRAY ( id INT[] PRIMARY KEY,  ptest2 text);
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"pktableforarray_pkey" for table "pktableforarray"
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( id SERIAL PRIMARY KEY, fids INT[] REFERENCES 
PKTABLEFORARRAY, ftest2 TEXT );
+ NOTICE:  CREATE TABLE will create implicit sequence "fktableforarray_id_seq" 
for serial column "fktableforarray.id"
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 
"fktableforarray_pkey" for table "fktableforarray"
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('{1,1}', 'A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('{1,2}', 'B');
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{1,1}', 'Product A');
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{1,2}', 'Product B');
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{0,1}', 'Product C');
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_fids_fkey"
+ DETAIL:  Key (fids)=({0,1}) is not present in table "pktableforarray".
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{2,1}', 'Product D');
+ ERROR:  insert or update on table "fktableforarray" violates foreign key 
constraint "fktableforarray_fids_fkey"
+ DETAIL:  Key (fids)=({2,1}) is not present in table "pktableforarray".
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
*** a/src/test/regress/sql/foreign_key.sql
--- b/src/test/regress/sql/foreign_key.sql
***************
*** 602,609 **** drop table pktable_base;
  
  -- 2 columns (1 table), mismatched types
  create table pktable_base(base1 int not null, base2 int);
- create table pktable(ptest1 inet, ptest2 inet[], primary key(base1, ptest1), 
foreign key(base2, ptest2) references
-                                              pktable(base1, ptest1)) inherits 
(pktable_base);
  create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), 
foreign key(base2, ptest2) references
                                               pktable(ptest1, base1)) inherits 
(pktable_base);
  create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), 
foreign key(ptest2, base2) references
--- 602,607 ----
***************
*** 943,945 **** begin;
--- 941,1111 ----
      update selfref set a = 456 where a = 123;
      select a, b from selfref;
  commit;
+ 
+ -- ARRAY FK
+ --
+ CREATE TABLE PKTABLEFORARRAY ( ptest1 int PRIMARY KEY, ptest2 text );
+ 
+ -- Insert test data into PKTABLEFORARRAY
+ INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test1');
+ INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test2');
+ INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test3');
+ INSERT INTO PKTABLEFORARRAY VALUES (4, 'Test4');
+ INSERT INTO PKTABLEFORARRAY VALUES (5, 'Test5');
+ 
+ -- Check alter table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+ ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ftest1) 
REFERENCES PKTABLEFORARRAY;
+ DROP TABLE FKTABLEFORARRAY;
+ 
+ -- Check create table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL REFERENCES 
PKTABLEFORARRAY, ftest2 int );
+ 
+ -- Insert successful rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 3);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 4);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES (NULL, 1);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{}', 5);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 6);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 7);
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 8);
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 9);
+ 
+ -- Insert failed rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 3);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 4);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 5);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 6);
+ INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 7);
+ INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 1);
+ 
+ -- Check FKTABLE
+ SELECT * FROM FKTABLEFORARRAY;
+ 
+ -- Delete a row from PK TABLE (must fail)
+ DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+ 
+ -- Check FKTABLE for removal of matched row
+ SELECT * FROM FKTABLEFORARRAY;
+ 
+ -- Update a row from PK TABLE (must fail)
+ UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+ 
+ -- Check FKTABLE for update of matched row
+ SELECT * FROM FKTABLEFORARRAY;
+ 
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE FKTABLEFORARRAYNOTNULL;
+ DROP TABLE FKTABLEFORARRAYMDIM;
+ 
+ -- Allowed references with actions 'RESTRICT' and 'NO ACTION'
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+ DROP TABLE FKTABLEFORARRAY;
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+ DROP TABLE FKTABLEFORARRAY;
+ 
+ -- Failed attempts of creating references with actions other than 'RESTRICT' 
and 'NO ACTION'
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] REFERENCES PKTABLEFORARRAY ON 
DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ 
+ -- Cleanup
+ DROP TABLE PKTABLEFORARRAY;
+ 
+ -- Repeat a similar test using CHAR(1) keys rather than INTEGER
+ CREATE TABLE PKTABLEFORARRAY ( ptest1 CHAR(1) PRIMARY KEY, ptest2 text );
+ 
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'Test A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('B', 'Test B');
+ INSERT INTO PKTABLEFORARRAY VALUES ('C', 'Test C');
+ 
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( ftest1 char(1)[] REFERENCES PKTABLEFORARRAY, 
ftest2 int );
+ 
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A"}', 1);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"B"}', 2);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"C"}', 3);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A","B","C"}', 4);
+ 
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"D"}', 5);
+ INSERT INTO FKTABLEFORARRAY VALUES ('{"A","B","D"}', 6);
+ 
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
+ 
+ -- Composite primary keys (unsupported)
+ CREATE TABLE PKTABLEFORARRAY ( id1 CHAR(1), id2 CHAR(1), ptest2 text, PRIMARY 
KEY (id1, id2) );
+ 
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'A', 'Test A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('A', 'B', 'Test B');
+ INSERT INTO PKTABLEFORARRAY VALUES ('B', 'C', 'Test B');
+ 
+ -- Must fail (cannot use arrays in composite foreign keys - use an array of 
composite types)
+ CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, 
FOREIGN KEY (fid1, fid2) REFERENCES PKTABLEFORARRAY (id1, id2));
+ 
+ -- Cleanup
+ DROP TABLE PKTABLEFORARRAY;
+ 
+ -- Test foreign key arrays with composite type
+ CREATE TYPE INVOICEID AS (year_part INTEGER, progressive_part INTEGER);
+ CREATE TABLE PKTABLEFORARRAY ( id INVOICEID PRIMARY KEY,  ptest2 text);
+ 
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2010, 99), 'Last invoice for 2010');
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2011, 1), 'First invoice for 2011');
+ INSERT INTO PKTABLEFORARRAY VALUES (ROW(2011, 2), 'Second invoice for 2011');
+ 
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( id SERIAL PRIMARY KEY, invoice_ids INVOICEID[] 
REFERENCES PKTABLEFORARRAY, ftest2 TEXT );
+ 
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2010,99)']::INVOICEID[], 'Product A');
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,1)','(2011,2)']::INVOICEID[], 'Product B');
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,2)']::INVOICEID[], 'Product C');
+ 
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,99)']::INVOICEID[], 'Product A');
+ INSERT INTO FKTABLEFORARRAY(invoice_ids, ftest2) VALUES 
(ARRAY['(2011,1)','(2010,1)']::INVOICEID[], 'Product B');
+ 
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
+ 
+ -- Create primary table with a primary key array
+ CREATE TABLE PKTABLEFORARRAY ( id INT[] PRIMARY KEY,  ptest2 text);
+ 
+ -- Create the foreign table
+ CREATE TABLE FKTABLEFORARRAY ( id SERIAL PRIMARY KEY, fids INT[] REFERENCES 
PKTABLEFORARRAY, ftest2 TEXT );
+ 
+ -- Populate the primary table
+ INSERT INTO PKTABLEFORARRAY VALUES ('{1,1}', 'A');
+ INSERT INTO PKTABLEFORARRAY VALUES ('{1,2}', 'B');
+ 
+ -- Insert valid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{1,1}', 'Product A');
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{1,2}', 'Product B');
+ 
+ -- Insert invalid rows into FK TABLE
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{0,1}', 'Product C');
+ INSERT INTO FKTABLEFORARRAY(fids, ftest2) VALUES ('{2,1}', 'Product D');
+ 
+ -- Cleanup
+ DROP TABLE FKTABLEFORARRAY;
+ DROP TABLE PKTABLEFORARRAY;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to