GSoC 2017: Foreign Key Arrays
Dear PostgreSQL hacker community,
I am working on Foreign Key Arrays as part of the Google Summer of Code
2017.
I will be logging my progress on this thread as I progress, twice a week
(Mondays and Fridays), so anyone who is willing to comment, please do.
*The Problem*
Foreign Key Arrays were introduced by Marco Nenciarini[1]/messages/by-id/1343842863.5162.4.camel@greygoo.devise-it.lan, however, the
proposed patch had some performance issues. Let's assume we have two
tables, table B has a foreign key array that references table A, any change
in table A (INSERT, UPDATE, DELETE) would trigger a referential
integrity check on table B. The current implementation uses sequential
scans to accomplish this. This limits the size of tables using Foreign Key
Arrays to ~100 records which is not practical in real life applications.
*The Proposed Solution*
Ultimately, as proposed by Tom Lane[2]/messages/by-id/28389.1351094795@sss.pgh.pa.us, we would like to replace the
sequential scan with a GIN-indexed scan which would greatly enhance the
performance.
To achieve this, introducing a number of new operators is required.
However, for the scope of the project, we will focus on the most basic case
where the Primary Keys are of pseudo-type anyelement and the Foreign Keys
are of pseudo-type anyarray, thus the main operator of concern will be
@>(anyarray,anyelement).
*Progress So Far*
The actual coding begins on 30th of May, till then I will use my time to
research, to settle the technical details of my plan.
- Collected resources about GIN indexing
- http://www.sigaev.ru/gin/README.txt
- https://wiki.postgresql.org/wiki/GIN_generalization
- src\backend\access\gin\README in the repo
- Cloned the git repo found @ https://github.com/postgres/postgres and
identified the main two files I will be concerned with. (I know I may need
to edit other files but these seem to where I will spend most of my summer)
- src/backend/commands/tablecmds.c
- src/backend/utils/ri_triggers.c
*I am yet to identify the files concerned with the GIN opclass. <-- if
anyone can help with this*
- read a little about op classes
- https://www.postgresql.org/docs/9.5/static/indexes-opclass.html
- explored the existing op classes in Postgres
*Next Step*
I still don't have a solid grasp of how I am going to approach creating an
operator, so I would like to experiment till the next report on creating a
very simple operator.
*I have attached the original proposal here.*
[1]: /messages/by-id/1343842863.5162.4.camel@greygoo.devise-it.lan
/messages/by-id/1343842863.5162.4.camel@greygoo.devise-it.lan
[2]: /messages/by-id/28389.1351094795@sss.pgh.pa.us
Best Regards,
Mark Rofail
Attachments:
On Mon, May 22, 2017 at 7:51 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
Cloned the git repo found @ https://github.com/postgres/postgres and
identified the main two files I will be concerned with. (I know I may need
to edit other files but these seem to where I will spend most of my summer)src/backend/commands/tablecmds.c
src/backend/utils/ri_triggers.cI am yet to identify the files concerned with the GIN opclass. <-- if anyone
can help with this
There's not only one GIN opclass. You can get a list like this:
select oid, * from pg_opclass where opcmethod = 2742;
Actually, you probably want to look for GIN opfamilies:
rhaas=# select oid, * from pg_opfamily where opfmethod = 2742;
oid | opfmethod | opfname | opfnamespace | opfowner
------+-----------+----------------+--------------+----------
2745 | 2742 | array_ops | 11 | 10
3659 | 2742 | tsvector_ops | 11 | 10
4036 | 2742 | jsonb_ops | 11 | 10
4037 | 2742 | jsonb_path_ops | 11 | 10
(4 rows)
To see which SQL functions are used to implement a particular
opfamily, use the OID from the previous step in a query like this:
rhaas=# select prosrc from pg_amop, pg_operator, pg_proc where
amopfamily = 2745 and amopopr = pg_operator.oid and oprcode =
pg_proc.oid;
prosrc
----------------
array_eq
arrayoverlap
arraycontains
arraycontained
(4 rows)
Then, you can look for those in the source tree. You can also search
for the associated support functions, e.g.:
rhaas=# select distinct amprocnum, prosrc from pg_amproc, pg_proc
where amprocfamily = 2745 and amproc = pg_proc.oid order by 1, 2;
amprocnum | prosrc
-----------+-----------------------
1 | bitcmp
1 | bpcharcmp
1 | btabstimecmp
1 | btboolcmp
1 | btcharcmp
1 | btfloat4cmp
1 | btfloat8cmp
1 | btint2cmp
1 | btint4cmp
1 | btint8cmp
1 | btnamecmp
1 | btoidcmp
1 | btoidvectorcmp
1 | btreltimecmp
1 | bttextcmp
1 | bttintervalcmp
1 | byteacmp
1 | cash_cmp
1 | date_cmp
1 | interval_cmp
1 | macaddr_cmp
1 | network_cmp
1 | numeric_cmp
1 | time_cmp
1 | timestamp_cmp
1 | timetz_cmp
2 | ginarrayextract
3 | ginqueryarrayextract
4 | ginarrayconsistent
6 | ginarraytriconsistent
(30 rows)
You might want to read https://www.postgresql.org/docs/devel/static/xindex.html
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
rhaas=# select oid, * from pg_opfamily where opfmethod = 2742;
oid | opfmethod | opfname | opfnamespace | opfowner
------+-----------+----------------+--------------+----------
2745 | 2742 | array_ops | 11 | 10
3659 | 2742 | tsvector_ops | 11 | 10
4036 | 2742 | jsonb_ops | 11 | 10
4037 | 2742 | jsonb_path_ops | 11 | 10
(4 rows)
I am particulary intrested in array_ops but I have failed in locating the
code behind it. Where is it reflected in the source code
Best Regards,
Mark Rofail
Hi, Mark!
On Tue, May 30, 2017 at 2:18 AM, Mark Rofail <markm.rofail@gmail.com> wrote:
rhaas=# select oid, * from pg_opfamily where opfmethod = 2742;
oid | opfmethod | opfname | opfnamespace | opfowner
------+-----------+----------------+--------------+----------
2745 | 2742 | array_ops | 11 | 10
3659 | 2742 | tsvector_ops | 11 | 10
4036 | 2742 | jsonb_ops | 11 | 10
4037 | 2742 | jsonb_path_ops | 11 | 10
(4 rows)I am particulary intrested in array_ops but I have failed in locating the
code behind it. Where is it reflected in the source code
Let's look what particular opclass is consisting of. Besides records in
pg_opfamily, it also contains records in pg_opclass, pg_amproc and pg_amop.
=# select * from pg_opclass where opcfamily = 2745;
opcmethod | opcname | opcnamespace | opcowner | opcfamily | opcintype |
opcdefault | opckeytype
-----------+-----------+--------------+----------+-----------+-----------+------------+------------
2742 | array_ops | 11 | 10 | 2745 | 2277 |
t | 2283
(1 row)
=# select * from pg_amproc where amprocfamily = 2745;
amprocfamily | amproclefttype | amprocrighttype | amprocnum |
amproc
--------------+----------------+-----------------+-----------+----------------------------
2745 | 2277 | 2277 | 2 |
pg_catalog.ginarrayextract
2745 | 2277 | 2277 | 3 |
ginqueryarrayextract
2745 | 2277 | 2277 | 4 |
ginarrayconsistent
2745 | 2277 | 2277 | 6 |
ginarraytriconsistent
(4 rows)
=# select * from pg_amop where amopfamily = 2745;
amopfamily | amoplefttype | amoprighttype | amopstrategy | amoppurpose |
amopopr | amopmethod | amopsortfamily
------------+--------------+---------------+--------------+-------------+---------+------------+----------------
2745 | 2277 | 2277 | 1 | s |
2750 | 2742 | 0
2745 | 2277 | 2277 | 2 | s |
2751 | 2742 | 0
2745 | 2277 | 2277 | 3 | s |
2752 | 2742 | 0
2745 | 2277 | 2277 | 4 | s |
1070 | 2742 | 0
(4 rows)
These records of system catalog are defined in special headers the source
code:
src/include/catalog/pg_amop.h
src/include/catalog/pg_amproc.h
src/include/catalog/pg_opclass.h
src/include/catalog/pg_opfamily.h
These records are written to system catalog during bootstrap process (see
src/backend/catalog/README).
As you can see pg_amproc records refer some procedures. Those procedures
are actually the majority of source code behind of opclass. Those
procedures are defined in src/backend/access/gin/ginarrayproc.c.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
src/include/catalog/pg_amop.h
src/include/catalog/pg_amproc.h
src/include/catalog/pg_opclass.h
src/include/catalog/pg_opfamily.h
Thanks to Alexander's reply I have been able to jump from catalog table to
table till I found the function I was looking for.
My goal is to add a new operator (@>(anyarray,anyelement)) to the (array_ops)
op class.
I am going to post the steps I took to locate the procedure, the following
is the trail of tables I followed.
pg_opfamily
pg_opfamily defines operator families.
Link to docs
<https://www.postgresql.org/docs/devel/static/catalog-pg-opfamily.html>
{
opfmethod; /* index access method opfamily is for */
opfname; /* name of this opfamily */
opfnamespace; /* namespace of this opfamily */
opfowner; /* opfamily owner */
}
gin=# select oid, * from pg_opfamily where opfmethod = 2742;
oid | opfmethod | opfname | opfnamespace | opfowner
------+-----------+----------------+--------------+----------
2745 | 2742 | array_ops | 11 | 10
3659 | 2742 | tsvector_ops | 11 | 10
4036 | 2742 | jsonb_ops | 11 | 10
4037 | 2742 | jsonb_path_ops | 11 | 10
(4 rows)
as this table defines operator families I won't need to modify them.
pg_opclass
pg_opclass defines index access method operator classes.
Link to docs
<https://www.postgresql.org/docs/current/static/catalog-pg-opclass.html>
{
opcmethod; /* index access method opclass is for */
opcname; /* name of this opclass */
opcnamespace; /* namespace of this opclass */
opcowner; /* opclass owner */
opcfamily; /* containing operator family */
opcintype; /* type of data indexed by opclass */
opcdefault; /* T if opclass is default for opcintype
opckeytype; /* type of data in index, or InvalidOid */
}
gin=# select * from pg_opclass where opcfamily = 2745;
opcmethod | opcname | opcnamespace | opcowner | opcfamily | opcintype |
opcdefault | opckeytype
-----------+-----------+--------------+----------+----------
-+-----------+------------+------------
2742 | array_ops | 11 | 10 | 2745 | 2277 | t
| 2283
(1 row)
as this table defines operator classes I won't need to modify them.
this led me to pg_amproc
pg_amproc
pg_amproc stores information about support procedures associated with
access method operator families.
Link to docs
<https://www.postgresql.org/docs/devel/static/catalog-pg-amproc.html>
{
amprocfamily; /* the index opfamily this entry is for */
amproclefttype; /* procedure's left input data type */
amprocrighttype; /* procedure's right input data type */
amprocnum[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* support procedure index */
amproc; /* OID of the proc */
}
gin=# select * from pg_amproc where amprocfamily = 2745;
amprocfamily | amproclefttype | amprocrighttype | amprocnum |
amproc
------------------+--------------------+--------------------
-+---------------+----------------------------
2745 | 2277 | 2277
| 2 | pg_catalog.ginarrayextract
2745 | 2277 | 2277
| 3 | ginqueryarrayextract
2745 | 2277 | 2277
| 4 | ginarrayconsistent
2745 | 2277 | 2277
| 6 | ginarraytriconsistent
(4 rows)
[1]: amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>
<https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>
as this table defines support procedures I won't need to modify them.
this led me to pg_amop
pg_amop
pg_amop stores information about operators associated with access method
operator families.
Link to docs
<https://www.postgresql.org/docs/devel/static/catalog-pg-amop.html>
{
amopfamily; /* the index opfamily this entry is for */
amoplefttype; /* operator's left input data type */
amoprighttype; /* operator's right input data type */
amopstrategy; /* operator strategy number */
amoppurpose; /* is operator for 's'earch or 'o'rdering? */
amopopr; /* the operator's pg_operator OID */
amopmethod; /* the index access method this entry is for
amopsortfamily; /* ordering opfamily OID, or 0 if search op
}
=# select * from pg_amop where amopfamily = 2745;
amopfamily | amoplefttype | amoprighttype | amopstrategy | amoppurpose |
amopopr | amopmethod | amopsortfamily
------------+--------------+---------------+--------------+-
------------+---------+------------+----------------
2745 | 2277 | 2277 | 1 | s |
2750 | 2742 | 0
2745 | 2277 | 2277 | 2 | s |
2751 | 2742 | 0
2745 | 2277 | 2277 | 3 | s |
2752 | 2742 | 0
2745 | 2277 | 2277 | 4 | s |
1070 | 2742 | 0
(4 rows)
I will need to add a record of my new operator to this table by appending
this line to src/include/catalog/pg_amop.h
DATA(insert (2745 2277 2277 1 s 2750 2742 0 ));
This will result in the following entry
=# select * from pg_amop where amopfamily = 2745;
amopfamily | amoplefttype | amoprighttype | amopstrategy | amoppurpose |
amopopr | amopmethod | amopsortfamily
------------+--------------+---------------+--------------+-
------------+---------+------------+----------------
2745 | 2277 | 2283 | 1 | s |
2750 | 2742 | 0
this led me to pg_operator
pg_operator
pg_operator stores information about operators.
Link to docs
<https://www.postgresql.org/docs/devel/static/catalog-pg-operator.html>
{
oprname; /* name of operator */
oprnamespace; /* OID of namespace containing this oper */
oprowner; /* operator owner */
oprkind; /* 'l', 'r', or 'b' */
oprcanmerge; /* can be used in merge join? */
oprcanhash; /* can be used in hash join? */
oprleft; /* left arg type, or 0 if 'l' oprkind */
oprright; /* right arg type, or 0 if 'r' oprkind */
oprresult; /* result datatype */
oprcom; /* OID of commutator oper, or 0 if none */
oprnegate; /* OID of negator oper, or 0 if none */
oprcode; /* OID of underlying function */
oprrest; /* OID of restriction estimator, or 0 */
oprjoin; /* OID of join estimator, or 0 */
}
postgres=# select * from pg_operator where oid = 2751;
oprname | oprnamespace | oprowner | oprkind | oprcanmerge | oprcanhash |
oprleft | oprright | oprresult | oprcom | oprnegate | oprcode |
oprrest | oprjoin
---------+--------------+----------+---------+-------------+
------------+---------+----------+-----------+--------+-----
------+---------------+--------------+------------------
@> | 11 | 10 | b | f | f |
2277 | 2277 | 16 | 2752 | 0 | arraycontains |
arraycontsel | arraycontjoinsel
(1 row)
I will need to add a record of my new operator to this table by appending
this line to src/include/catalog/pg_operator.h
However, as this is dependent on the procedure I have yet to create there
are still uknown values
DATA(insert OID = <uniqueProcId> ( "@>" PGNSP PGUID b f f 2277 2283 16
2752 0 arraycontainselem ???? ???? ));
DESCR("contains");
#define OID_ARRAY_CONTAINS_OP <uniqueProcId>
This will lead to this entry
oprname | oprnamespace | oprowner | oprkind | oprcanmerge | oprcanhash |
oprleft | oprright | oprresult | oprcom | oprnegate | oprcode |
oprrest | oprjoin
---------+--------------+----------+---------+-------------+
------------+---------+----------+-----------+--------+-----
------+---------------+--------------+------------------
@> | 11 | 10 | b | f | f |
2277 | 2283 | 16 | 2752 | 0 | arraycontainselem |
???? | ????
(1 row)
this led me to pg_proc
pg_proc
pg_proc stores information about functions (or procedures)
Link to docs
<https://www.postgresql.org/docs/devel/static/catalog-pg-proc.html>
{
proname; /* procedure name */
pronamespace; /* OID of namespace containing this proc */
proowner; /* procedure owner */
prolang; /* OID of pg_language entry */
procost; /* estimated execution cost */
prorows; /* estimated # of rows out (if proretset) */
provariadic; /* element type of variadic array, or 0 */
protransform; /* transforms calls to it during planning */
proisagg; /* is it an aggregate? */
proiswindow; /* is it a window function? */
prosecdef; /* security definer */
proleakproof; /* is it a leak-proof function? */
proisstrict; /* strict with respect to NULLs? */
proretset; /* returns a set? */
provolatile; /* see PROVOLATILE_ categories below */
proparallel; /* see PROPARALLEL_ categories below */
pronargs; /* number of arguments */
pronargdefaults; /* number of arguments with defaults */
prorettype; /* OID of result type */
proargtypes; /* parameter types (excludes OUT params) */
proallargtypes[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* all param types (NULL if IN only) */
proargmodes[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* parameter modes (NULL if IN only) */
proargnames[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* parameter names (NULL if no names) */
proargdefaults; /* list of expression trees for argument
protrftypes[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* types for which to apply transforms */
prosrc; /* procedure source text */
probin; /* secondary procedure info (can be NULL) */
proconfig[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* procedure-local GUC settings */
proacl[1]amprocnum refers to this table <https://www.postgresql.org/docs/10/static/xindex.html#xindex-gin-support-table>; /* access permissions */
}
postgres=# select * from pg_proc where oid = 2748;
proname | pronamespace | proowner | prolang | procost | prorows |
provariadic | protransform | proisagg | proiswindow | prosecdef |
proleakproof | proisstrict | proretset | provolatile | proparallel | pron
args | pronargdefaults | prorettype | proargtypes | proallargtypes |
proargmodes | proargnames | proargdefaults | protrftypes | prosrc |
probin | proconfig | proacl
---------------+--------------+----------+---------+--------
-+---------+-------------+--------------+----------+--------
-----+-----------+--------------+-------------+-----------+-
------------+-------------+-----
-----+-----------------+------------+-------------+---------
-------+-------------+-------------+----------------+-------
------+---------------+--------+-----------+--------
arraycontains | 11 | 10 | 12 | 1 | 0 |
0 | - | f | f | f | f
| t | f | i | s |
2 | 0 | 16 | 2277 2277 | |
| | | | arraycontains |
| |
(1 row)
I have yet to study this table thoroughly.
This finally led me to the arraycontains procedure in src/backend/utils/adt/
arrayfuncs.c
Datum
arraycontains(PG_FUNCTION_ARGS)
{
AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
Oid collation = PG_GET_COLLATION();
bool result;
result = array_contain_compare(array2, array1, collation, true,
&fcinfo->flinfo->fn_extra);
/* Avoid leaking memory when handed toasted input. */
AARR_FREE_IF_COPY(array1, 0);
AARR_FREE_IF_COPY(array2, 1);
PG_RETURN_BOOL(result);
}
This corrosponds to the operator @<(anyarray, anyarray) which is the
generalised form of my proposed operator @<(anyarray, anyelement).
Studying the syntax will help me produce a function that follows the
postgres style rules.
• After finding the arraycontains function, I implemented
arraycontainselem that corresponds to the operator @<(anyarray,
anyelem)
◦ Please read the attached patch file to view my progress.
• In addition to src/backend/utils/adt/arrayfuncs.c where I
implemented arraycontainselem.
◦ I also edited pg_amop (src/include/catalog/pg_amop.h) since
it stores information about operators associated with access method
operator families.
+DATA(insert ( 2745 2277 2283 2 s 2753 2742 0 ));
{
2745: Oid amopfamily; (denotes gin array_ops)
277: Oid amoplefttype; (denotes anyaray)
2283: Oid amoprighttype; (denotes anyelem)
5: int16 amopstrategy; /* operator strategy number */ (denotes the new
startegy that is yet to be created)
's': char amoppurpose; (denotes 's' for search)
2753: Oid amopopr; (denotes the new operator Oid)
2742: Oid amopmethod;(denotes gin)
0: Oid amopsortfamily; (0 since search operator)
}
◦ And pg_operator (src/include/catalog/pg_operator.h) since it
stores information about operators.
+DATA(insert OID = 2753 ( "@>" PGNSP PGUID b f f 2277 2283 16 0 0
arraycontainselem 0 0 ));
{
"@>": NameData oprname; /* name of operator */
Oid oprnamespace; /* OID of namespace containing this oper */
Oid oprowner; /* operator owner */
'b': char oprkind; /* 'l', 'r', or 'b' */ (denotes infix)
'f': bool oprcanmerge; /* can be used in merge join? */
'f': bool oprcanhash; /* can be used in hash join? */
277: Oid oprleft; (denotes anyaray)
2283: Oid oprright; (denotes anyelem)
16: Oid oprresult; (denotes boolean)
0: Oid oprcom; /* OID of commutator oper, or 0 if none */ (needs to be
revisited)
0: Oid oprnegate; /* OID of negator oper, or 0 if none */ (needs to be
revisited)
arraycontainselem: regproc oprcode; /* OID of underlying function */
0: regproc oprrest; /* OID of restriction estimator, or 0 */
0: regproc oprjoin; /* OID of join estimator, or 0 */
}
Attachments:
elemOperatorV2.patchtext/x-patch; charset=US-ASCII; name=elemOperatorV2.patchDownload
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index cc7435e030..14fedc8066 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -110,7 +111,8 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
if (nelems > 0)
*searchMode = GIN_SEARCH_MODE_DEFAULT;
else /* everything contains the empty set */
@@ -171,6 +173,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +261,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index d9c8aa569c..e1ff6d33b5 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4215,6 +4215,40 @@ arraycontains(PG_FUNCTION_ARGS)
}
Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ Datum *elem = PG_GETARG_DATUM(0);
+ AnyArrayType *array1;
+ AnyArrayType *array2 = PG_GETARG_ANY_ARRAY(1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ int16 typlen;
+ bool typbyval;
+ char typalign;
+ int nelems;
+
+ /* we have one element */
+ nelems= 1;
+
+ /* get required info about the element type */
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &typlen, &typbyval, &typalign);
+
+ /* now build the array */
+ array1 = construct_array(&elem, nelems,collation, &typlen, &typbyval, &typalign);
+
+ result = array_contain_compare(array2, array1, collation, true,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ PG_FREE_IF_COPY(elem,0);
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
arraycontained(PG_FUNCTION_ARGS)
{
AnyArrayType *array1 = PG_GETARG_ANY_ARRAY(0);
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index da0228de6b..2da9002577 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -687,6 +687,8 @@ DATA(insert ( 2595 718 600 15 o 3291 783 1970 ));
*/
DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
+//TODO link the operator's pg_operator OID
+DATA(insert ( 2745 2277 2283 5 s 2753 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ccbb17efec..626a0b1c49 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1567,6 +1567,9 @@ DESCR("overlaps");
DATA(insert OID = 2751 ( "@>" PGNSP PGUID b f f 2277 2277 16 2752 0 arraycontains arraycontsel arraycontjoinsel ));
DESCR("contains");
#define OID_ARRAY_CONTAINS_OP 2751
+DATA(insert OID = 2753 ( "@>" PGNSP PGUID b f f 2277 2283 16 2753 0 arraycontainselem 0 0 ));
+DESCR("containselem");
+#define OID_ARRAY_CONTAINS_ELEM_OP 2753
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
I was idly following along in GSoC 2017: Foreign Key Arrays
when I noticed this:
=# select * from pg_amproc where amprocfamily = 2745;
amprocfamily | amproclefttype | amprocrighttype | amprocnum |
amproc
--------------+----------------+-----------------+-----------+
2745 | 2277 | 2277 | 2 |
pg_catalog.ginarrayextract
2745 | 2277 | 2277 | 3 |
ginqueryarrayextract
...
where only ginarrayextract is schema-qualified. It seems to be
regproc's output procedure doing it:
=# select 2743::regproc, 2774::regproc;
regproc | regproc
----------------------------+----------------------
pg_catalog.ginarrayextract | ginqueryarrayextract
The manual says regproc "will display schema-qualified names on output
if the object would not be found in the current search path without
being qualified."
Is regproc displaying the schema in this case because there are two
overloaded flavors of ginarrayextract, though both are in pg_catalog?
Could it be searching for the object by name, ignoring the argument
signature, and just detecting that it hit one with a different OID first?
-Chap
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Chapman Flack <chap@anastigmatix.net> writes:
The manual says regproc "will display schema-qualified names on output
if the object would not be found in the current search path without
being qualified."
That's less than the full truth :-(
Is regproc displaying the schema in this case because there are two
overloaded flavors of ginarrayextract, though both are in pg_catalog?
Yes, see the test in regprocout:
* Would this proc be found (uniquely!) by regprocin? If not,
* qualify it.
Of course, in a situation like this, schema-qualification is not enough to
save the day; regprocin will still fail because the name is ambiguous.
You really need to use regprocedure not regproc if you want any guarantees
about the results.
(The fact that we have regproc at all is a bit of a historical accident,
caused by some limitations of the bootstrap mode.)
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
*Updates till now:*
- added a record to pg_proc (src/include/catalog/pg_proc.h)
- modified opr_sanity regression check expected results
- implemented a low-level function called `array_contains_elem` as an
equivalent to `array_contain_compare` but accepts anyelement instead of
anyarray as the right operand. This is more efficient than constructing an
array and then immediately deconstructing it.
*Questions:*
- I'd like to check that anyelem and anyarray have the same element
type. but anyelem is obtained from PG_FUNCTION_ARGS as a Datum. How can
I make such a check?
Best Regards,
Mark Rofail
Attachments:
elemOperatorV3.patchtext/x-patch; charset=US-ASCII; name=elemOperatorV3.patchDownload
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index cc7435e030..214aac8fba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -43,7 +44,7 @@ ginarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(array,
@@ -110,7 +111,8 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
if (nelems > 0)
*searchMode = GIN_SEARCH_MODE_DEFAULT;
else /* everything contains the empty set */
@@ -171,6 +173,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +261,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index d9c8aa569c..8009ab5acb 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,107 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid collation,
+ void **fn_extra)
+{
+ Oid element_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != element_type)
+ {
+ typentry = lookup_type_cache(element_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(element_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ result = array_contains_elem(array, elem, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+ PG_FREE_IF_COPY( &elem, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index da0228de6b..4967a388e8 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,6 +689,7 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
+DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 ));
/*
* btree enum_ops
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ccbb17efec..cc56d61bfb 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 6c44def6e6..66ba68e9ef 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1d7629f84e..dc98a8c8f9 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1801,6 +1801,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1868,7 +1869,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
On Sun, Jun 18, 2017 at 12:41 AM, Mark Rofail <markm.rofail@gmail.com>
wrote:
*Questions:*
- I'd like to check that anyelem and anyarray have the same element
type. but anyelem is obtained from PG_FUNCTION_ARGS as a Datum. How
can I make such a check?
As I know, it's implicitly checked during query analyze stage. You don't
have to implement your own check inside function implementation.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Okay, so major breakthrough.
*Updates:*
- The operator @>(anyarray, anyelement) is now functional
- The segmentation fault was due to applying PG_FREE_IF_COPY on a
datum when it should only be applied on TOASTed inputs
- The only problem now is if for example you apply the operator as
follows '{AAAAAAAAAA646'}' @> 'AAAAAAAAAA646' it maps to @>(anyarray,
anyarray) since 'AAAAAAAAAA646' is interpreted as char[] instead of Text
- Added some regression tests (src/test/regress/sql/arrays.sql) and
their results(src/test/regress/expected/arrays.out)
- wokred on the new GIN strategy, I don't think it would vary much from
GinContainsStrategy.
*What I plan to do:*
- I need to start working on the Referential Integrity code but I don't
where to start
Best Regards,
Mark Rofail
Attachments:
elemOperatorV3_1.patchtext/x-patch; charset=US-ASCII; name=elemOperatorV3_1.patchDownload
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index cc7435e030..a1b3f53ed9 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -110,6 +111,11 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
break;
+ case GinContainsElemStrategy:
+ /* only items that match the queried element
+ are considered candidate */
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
case GinContainsStrategy:
if (nelems > 0)
*searchMode = GIN_SEARCH_MODE_DEFAULT;
@@ -171,6 +177,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +265,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index d9c8aa569c..c563aa564e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,117 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid element_type,
+ bool element_isnull, Oid collation, void **fn_extra)
+{
+ Oid arr_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ if (arr_type != element_type)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare different element types")));
+
+ if (element_isnull)
+ return false;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arr_type)
+ {
+ typentry = lookup_type_cache(arr_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arr_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool element_isnull = PG_ARGISNULL(1);
+ bool result;
+
+ result = array_contains_elem(array, elem, element_type,
+ element_isnull, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index da0228de6b..4967a388e8 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,6 +689,7 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
+DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 ));
/*
* btree enum_ops
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ccbb17efec..cc56d61bfb 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 6c44def6e6..66ba68e9ef 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c730563f03..f9932a5335 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -737,6 +737,17 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -761,6 +772,19 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE i @> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -942,6 +966,11 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -962,6 +991,15 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -979,6 +1017,14 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1d7629f84e..dc98a8c8f9 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1801,6 +1801,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1868,7 +1869,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 25dd4e2c6d..39189ce6e3 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -313,8 +313,10 @@ SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @> 32 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -325,12 +327,15 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @> NULL ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @> 'AAAAAAAA72908' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @> 'AAAAAAAAAA646' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
Mark Rofail wrote:
Okay, so major breakthrough.
*Updates:*
- The operator @>(anyarray, anyelement) is now functional
- The segmentation fault was due to applying PG_FREE_IF_COPY on a
datum when it should only be applied on TOASTed inputs
- The only problem now is if for example you apply the operator as
follows '{AAAAAAAAAA646'}' @> 'AAAAAAAAAA646' it maps to @>(anyarray,
anyarray) since 'AAAAAAAAAA646' is interpreted as char[] instead of Text
- Added some regression tests (src/test/regress/sql/arrays.sql) and
their results(src/test/regress/expected/arrays.out)
- wokred on the new GIN strategy, I don't think it would vary much from
GinContainsStrategy.
OK, that's great.
*What I plan to do:*
- I need to start working on the Referential Integrity code but I don't
where to start
You need to study the old patch posted by Marco Nenciarini.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
*What I did:*
- read into the old patch but couldn't apply it since it's quite old. It
needs to be rebased and that's what I am working on. It's a lot of work.
- incomplete patch can be found attached here
*Bugs*
- problem with the @>(anyarray, anyelement) opertator: if for example,
you apply the operator as follows '{AAAAAAAAAA646'}' @> 'AAAAAAAAAA646' it
maps to @>(anyarray, anyarray) since 'AAAAAAAAAA646' is interpreted as
char[] instead of Text
*Suggestion:*
- since I needed to check if the Datum was null and its type, I had to
do it in the arraycontainselem and pass it as a parameter to the underlying
function array_contains_elem. I'm proposing to introduce a new struct like
ArrayType, but ElementType along all with brand new MACROs to make dealing
with anyelement easier in any polymorphic context.
Best Regards,
Mark Rofail
On Tue, Jun 20, 2017 at 12:19 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Show quoted text
Mark Rofail wrote:
Okay, so major breakthrough.
*Updates:*
- The operator @>(anyarray, anyelement) is now functional
- The segmentation fault was due to applying PG_FREE_IF_COPY on a
datum when it should only be applied on TOASTed inputs
- The only problem now is if for example you apply the operator as
follows '{AAAAAAAAAA646'}' @> 'AAAAAAAAAA646' it maps to@>(anyarray,
anyarray) since 'AAAAAAAAAA646' is interpreted as char[] instead
of Text
- Added some regression tests (src/test/regress/sql/arrays.sql) and
their results(src/test/regress/expected/arrays.out)
- wokred on the new GIN strategy, I don't think it would vary muchfrom
GinContainsStrategy.
OK, that's great.
*What I plan to do:*
- I need to start working on the Referential Integrity code but I
don't
where to start
You need to study the old patch posted by Marco Nenciarini.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
incomplete-Array-ELEMENT-foreign-key-v2-REBASED-ddb5fdc.patchtext/x-patch; charset=US-ASCII; name=incomplete-Array-ELEMENT-foreign-key-v2-REBASED-ddb5fdc.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea655a10a8..712f631e88 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2288,6 +2288,14 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>confiselement</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>If a foreign key, is it an array <literal>ELEMENT</literal>
+ foreign key?</entry>
+ </row>
+
+ <row>
<entry><structfield>coninhcount</structfield></entry>
<entry><type>int4</type></entry>
<entry></entry>
@@ -2324,6 +2332,18 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>confelement</structfield></entry>
+ <entry><type>bool[]</type></entry>
+ <entry></entry>
+ <entry>
+ If a foreign key, list of booleans expressing which columns
+ are array <literal>ELEMENT</literal> columns; see
+ <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for details
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>conpfeqop</structfield></entry>
<entry><type>oid[]</type></entry>
<entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b05a9c2150..c1c847bc7e 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -881,7 +881,112 @@ CREATE TABLE order_items (
<xref linkend="sql-createtable">.
</para>
</sect2>
-
+
+ <sect2 id="ddl-constraints-element-fk">
+ <title>Array ELEMENT Foreign Keys</title>
+
+ <indexterm>
+ <primary>ELEMENT foreign key</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>Array ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>referential integrity</primary>
+ </indexterm>
+
+ <para>
+ Another option you have with foreign keys is to use a
+ referencing column which is an array of elements with
+ the same type (or a compatible one) as the referenced
+ column in the related table. This feature is called
+ <firstterm>array element foreign key</firstterm> and is implemented
+ in PostgreSQL with <firstterm>ELEMENT foreign key constraints</firstterm>,
+ as described in the following example:
+
+ <programlisting>
+ CREATE TABLE drivers (
+ driver_id integer PRIMARY KEY,
+ first_name text,
+ last_name text,
+ ...
+ );
+
+ CREATE TABLE races (
+ race_id integer PRIMARY KEY,
+ title text,
+ race_day DATE,
+ ...
+ final_positions integer[] <emphasis>ELEMENT REFERENCES drivers</emphasis>
+ );
+ </programlisting>
+
+ The above example uses an array (<literal>final_positions</literal>)
+ to store the results of a race: for each of its elements
+ a referential integrity check is enforced on the
+ <literal>drivers</literal> table.
+ Note that <literal>ELEMENT REFERENCES</literal> is an extension
+ of PostgreSQL and it is not included in the SQL standard.
+ </para>
+
+ <para>
+ Even though the most common use case for array <literal>ELEMENT</literal>
+ foreign keys is on a single column key, you can define an <quote>array
+ <literal>ELEMENT</literal> foreign key constraint</quote> on a group
+ of columns. As the following example shows, it must be written in table
+ constraint form:
+
+ <programlisting>
+ CREATE TABLE available_moves (
+ kind text,
+ move text,
+ description text,
+ PRIMARY KEY (kind, move)
+ );
+
+ CREATE TABLE paths (
+ description text,
+ kind text,
+ moves text[],
+ <emphasis>FOREIGN KEY (kind, ELEMENT moves) REFERENCES available_moves (kind, move)</emphasis>
+ );
+
+ INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');
+ INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');
+ INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');
+ INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');
+ INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');
+ INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');
+ INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');
+ INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');
+
+ INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');
+ INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');
+ </programlisting>
+
+ On top of standard foreign key requirements,
+ array <literal>ELEMENT</literal> foreign key constraints
+ require that the referencing column is an array of a compatible
+ type of the corresponding referenced column.
+ </para>
+
+ <para>
+ For more detailed information on array <literal>ELEMENT</literal>
+ foreign key options and special cases, please refer to the documentation
+ for <xref linkend="sql-createtable-foreign-key"> and
+ <xref linkend="sql-createtable-element-foreign-key-constraints">.
+ </para>
+
+ </sect2>
+
<sect2 id="ddl-constraints-exclusion">
<title>Exclusion Constraints</title>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b15c19d3d0..1d7749ce38 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -65,7 +65,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> |
- REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
+ [ELEMENT] REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -76,7 +76,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
- FOREIGN KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
+ FOREIGN KEY ( [ELEMENT] <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -779,10 +779,11 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
- <varlistentry>
+ <varlistentry id="sql-createtable-foreign-key" xreflabel="FOREIGN KEY">
<term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
- <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
+ <term>
+ <literal>FOREIGN KEY ( [ELEMENT] <replaceable class="parameter">column</replaceable> [, ... ] )
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
[ MATCH <replaceable class="parameter">matchtype</replaceable> ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ]
@@ -806,6 +807,19 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</para>
<para>
+ In case the column name <replaceable class="parameter">column</replaceable>
+ is prepended with the <literal>ELEMENT</literal> keyword and <replaceable
+ class="parameter">column</replaceable> is an array of elements compatible
+ with the corresponding <replaceable class="parameter">refcolumn</replaceable>
+ in <replaceable class="parameter">reftable</replaceable>, an
+ array <literal>ELEMENT</literal> foreign key constraint is put in place
+ (see <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for more information).
+ Multi-column keys with more than one <literal>ELEMENT</literal> column
+ are currently not allowed.
+ </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
@@ -868,7 +882,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<para>
Delete any rows referencing the deleted row, or update the
values of the referencing column(s) to the new values of the
- referenced columns, respectively.
+ referenced columns, respectively. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -877,7 +892,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<term><literal>SET NULL</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set the referencing column(s) to null. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -889,7 +905,9 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
Set the referencing column(s) to their default values.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
- </para>
+ Currently not supported with array <literal>ELEMENT</literal>
+ foreign keys.
+ </para>
</listitem>
</varlistentry>
</variablelist>
@@ -904,6 +922,60 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-element-foreign-key-constraints" xreflabel="ELEMENT REFERENCES">
+ <term><literal>ELEMENT REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
+ <listitem>
+ <para>
+ The <literal>ELEMENT REFERENCES</literal> definition specifies
+ an <quote>array <literal>ELEMENT</literal> foreign key</quote>,
+ a special kind of foreign key
+ constraint requiring the referencing column to be an array of elements
+ of the same type (or a compatible one) as the referenced column
+ in the referenced table. The value of each element of the
+ <replaceable class="parameter">refcolumn</replaceable> array
+ will be matched against some row of <replaceable
+ class="parameter">reftable</replaceable>.
+ </para>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys are an extension
+ of PostgreSQL and are not included in the SQL standard.
+ </para>
+
+ <para>
+ Even with <literal>ELEMENT</literal> foreign keys, modifications
+ in the referenced column can trigger actions to be performed on
+ the referencing array.
+ Similarly to standard foreign keys, you can specify these
+ actions using the <literal>ON DELETE</literal> and
+ <literal>ON UPDATE</literal> clauses.
+ However, only the two following actions for each clause are
+ currently allowed:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>NO ACTION</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints. This is the default action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFERRABLE</literal></term>
<term><literal>NOT DEFERRABLE</literal></term>
@@ -1843,6 +1915,16 @@ CREATE TABLE cities_ab_10000_to_100000
</refsect2>
<refsect2>
+ <title id="sql-createtable-foreign-key-arrays">Array <literal>ELEMENT</literal> Foreign Keys</title>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys and the
+ <literal>ELEMENT REFERENCES</literal> clause
+ are a <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect2>
+
+ <refsect2>
<title><literal>PARTITION BY</> Clause</title>
<para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..a25ccd084c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2099,7 +2099,9 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
NULL,
NULL,
0,
- ' ',
+ false,
+ NULL,
+ ' ',
' ',
' ',
NULL, /* not an exclusion constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 027abd56b0..a2e8baba55 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1212,6 +1212,8 @@ index_constraint_create(Relation heapRelation,
NULL,
NULL,
0,
+ false,
+ NULL,
' ',
' ',
' ',
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 98bcfa08c6..0c731b1085 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1226,7 +1226,9 @@ CREATE VIEW referential_constraints AS
WHEN 'd' THEN 'SET DEFAULT'
WHEN 'r' THEN 'RESTRICT'
WHEN 'a' THEN 'NO ACTION' END
- AS character_data) AS delete_rule
+ AS character_data) AS delete_rule,
+
+ con.confiselement AS is_element
FROM (pg_namespace ncon
INNER JOIN pg_constraint con ON ncon.oid = con.connamespace
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46d3f..c99b7c6cad 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -63,6 +63,8 @@ CreateConstraintEntry(const char *constraintName,
const Oid *ppEqOp,
const Oid *ffEqOp,
int foreignNKeys,
+ bool confisElement,
+ const bool *foreignElement,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
@@ -82,6 +84,7 @@ CreateConstraintEntry(const char *constraintName,
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *confelementArray;
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
@@ -177,6 +180,7 @@ CreateConstraintEntry(const char *constraintName,
values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+ values[Anum_pg_constraint_coniselement - 1] = BoolGetDatum(confisElement);
if (conkeyArray)
values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
@@ -188,6 +192,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_confkey - 1] = true;
+ if (confelementArray)
+ values[Anum_pg_constraint_confelement - 1] = PointerGetDatum(confelementArray);
+ else
+ nulls[Anum_pg_constraint_confelement - 1] = true;
+
if (conpfeqopArray)
values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7d9c769b06..03dfa2ea5e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -41,6 +41,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -6995,6 +6996,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Relation pkrel;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
+ bool fkattelement[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
@@ -7082,6 +7084,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
+ MemSet(fkattelement, 0, sizeof(fkattelement));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
@@ -7094,6 +7097,39 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkattnum, fktypoid);
/*
+ * If an array ELEMENT FK, decode the content of
+ * the fk_element_attrs array.
+ */
+ if (fkconstraint->fk_is_element)
+ {
+ ListCell *l;
+ int attnum;
+ bool element_found = false;
+
+ attnum = 0;
+ foreach(l, fkconstraint->fk_element_attrs)
+ {
+ if (lfirst_int(l)) {
+
+ /*
+ * Currently, the ELEMENT flag cannot be set on more than
+ * one column.
+ */
+ if (element_found) {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("array ELEMENT foreign keys support only one ELEMENT column")));
+ }
+
+ fkattelement[attnum] = true;
+ element_found = true;
+ }
+ attnum++;
+ }
+
+ }
+
+ /*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
@@ -7141,6 +7177,22 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
old_check_ok = (fkconstraint->old_conpfeqop != NIL);
Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
+ /* Enforce array ELEMENT foreign key restrictions */
+ if (fkconstraint->fk_is_element)
+ {
+ /*
+ * Array ELEMENT foreign keys support only NO ACTION and
+ * RESTRICT actions
+ */
+ 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("array ELEMENT foreign keys only support NO ACTION and RESTRICT actions")));
+ }
+
for (i = 0; i < numpks; i++)
{
Oid pktype = pktypoid[i];
@@ -7156,6 +7208,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid ffeqop;
int16 eqstrategy;
Oid pfeqop_right;
+ Oid fk_element_type = InvalidOid;
/* We need several fields out of the pg_opclass entry */
cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
@@ -7189,6 +7242,31 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
eqstrategy, opcintype, opcintype, opfamily);
+ if (fkattelement[i])
+ {
+ /*
+ * For every array ELEMENT FK, look if an equality operator that
+ * takes exactly the FK element type exists. Assume we should
+ * look through any domain here.
+ */
+ fk_element_type = get_base_element_type(fktype);
+ if (!OidIsValid(fk_element_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has type %s which is not an array type.",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktype))));
+
+ pfeqop = get_opfamily_member(opfamily, opcintype, fk_element_type,
+ eqstrategy);
+ pfeqop_right = fk_element_type;
+ ffeqop = ARRAY_EQ_OP;
+ }
+ else
+ {
+
/*
* Are there equality operators that take exactly the FK type? Assume
* we should look through any domain here.
@@ -7208,6 +7286,26 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
/* keep compiler quiet */
pfeqop_right = InvalidOid;
ffeqop = InvalidOid;
+ /*
+ * Are there equality operators that take exactly the FK type?
+ * Assume we should look through any domain here.
+ */
+ fktyped = getBaseType(fktype);
+
+ pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+ eqstrategy);
+ if (OidIsValid(pfeqop))
+ {
+ pfeqop_right = fktyped;
+ ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+ eqstrategy);
+ }
+ else
+ {
+ /* keep compiler quiet */
+ pfeqop_right = InvalidOid;
+ ffeqop = InvalidOid;
+ }
}
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
@@ -7225,25 +7323,46 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid target_typeids[2];
input_typeids[0] = pktype;
- input_typeids[1] = fktype;
+ if (fkattelement[i])
+ input_typeids[1] = fk_element_type;
+ else
+ input_typeids[1] = fktype;
target_typeids[0] = opcintype;
target_typeids[1] = opcintype;
if (can_coerce_type(2, input_typeids, target_typeids,
COERCION_IMPLICIT))
{
- pfeqop = ffeqop = ppeqop;
+ pfeqop = ppeqop;
pfeqop_right = opcintype;
++ /*
++ * In case of an array ELEMENT FK, the ffeqop must be left
++ * untouched; otherwise we use the primary equality operator.
++ */
++ if (!fkattelement[i])
++ ffeqop = ppeqop;
}
}
+ /*
+ * In case of an array ELEMENT FK, make sure TYPECACHE_EQ_OPR exists
+ * for the FK element_type and it is compatible with pfeqop
+ */
+ if (fkattelement[i] && OidIsValid(pfeqop))
+ {
+ TypeCacheEntry *typentry = lookup_type_cache(fk_element_type,
+ TYPECACHE_EQ_OPR);
+ if (!OidIsValid(typentry->eq_opr)
+ || !equality_ops_are_compatible(typentry->eq_opr, pfeqop))
+ /* Error: incompatible operators */
+ pfeqop = InvalidOid;
+ }
+
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("foreign key constraint \"%s\" "
- "cannot be implemented",
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
fkconstraint->conname),
- errdetail("Key columns \"%s\" and \"%s\" "
- "are of incompatible types: %s and %s.",
+ errdetail("Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktype),
@@ -7274,8 +7393,16 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* column types to the right (foreign) operand type of the pfeqop.
* We may assume that pg_constraint.conkey is not changing.
*/
- old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
- new_fktype = fktype;
+ if (fkattelement[i])
+ {
+ old_fktype = get_base_element_type(tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid);
+ new_fktype = fk_element_type;
+ }
+ else
+ {
+ old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+ new_fktype = fktype;
+ }
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
&old_castfunc);
new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
@@ -7345,6 +7472,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
ppeqoperators,
ffeqoperators,
numpks,
+ fkconstraint->fk_is_element,
+ fkattelement,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
On Mon, Jun 26, 2017 at 2:26 AM, Mark Rofail <markm.rofail@gmail.com> wrote:
*What I did:*
- read into the old patch but couldn't apply it since it's quite old.
It needs to be rebased and that's what I am working on. It's a lot of
work.
- incomplete patch can be found attached hereHave you met any particular problem here? Or is it just a lot of
mechanical work?
*Bugs*
- problem with the @>(anyarray, anyelement) opertator: if for example,
you apply the operator as follows '{AAAAAAAAAA646'}' @> 'AAAAAAAAAA646' it
maps to @>(anyarray, anyarray) since 'AAAAAAAAAA646' is interpreted as
char[] instead of TextI don't think it is bug. When types are not specified explicitly, then
optimizer do its best on guessing them. Sometimes results are
counterintuitive to user. But that is not bug, it's probably a room for
improvement. And I don't think this improvement should be subject of this
GSoC. Anyway, array FK code should use explicit type cast, and then you
wouldn't meet this problem.
On the other hand, you could just choose another operator name for
arraycontainselem.
Then such problem probably wouldn't occur.
*Suggestion:*
- since I needed to check if the Datum was null and its type, I had to
do it in the arraycontainselem and pass it as a parameter to the underlying
function array_contains_elem. I'm proposing to introduce a new struct like
ArrayType, but ElementType along all with brand new MACROs to make dealing
with anyelement easier in any polymorphic context.You don't need to do explicit check for nulls, because arraycontainselem
is marked as strict function. Executor never pass null inputs to your
function if its declared as strict. See evaluate_function().
Also, during query planning it's checked that all polymorphic are
consistent between each other. See
https://www.postgresql.org/docs/devel/static/extend-type-system.html#extend-types-polymorphic
and check_generic_type_consistency() for details.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Mon, Jun 26, 2017 at 6:44 PM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
Have you met any particular problem here? Or is it just a lot of
mechanical work?
Just A LOT of mechanictal work, thankfully. The patch is now rebased and
all regress tests have passed (even the element_foreign_key). Please find
the patch below !
I don't think it is bug.
That's good news !
*What I plan to do next *
- study ri_triggers.c (src/backend/utils/adt/ri_triggers.c) since this
is where the new RI code will reside
Best Regards,
Mark Rofail
Attachments:
Array-ELEMENT-foreign-key-v2-REBASED-f0256c7.patchtext/x-patch; charset=US-ASCII; name=Array-ELEMENT-foreign-key-v2-REBASED-f0256c7.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea655a10a8..5c32a4f20a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2299,6 +2299,14 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>confiselement</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>If a foreign key, is it an array <literal>ELEMENT</literal>
+ foreign key?</entry>
+ </row>
+
+ <row>
<entry><structfield>connoinherit</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
@@ -2324,6 +2332,18 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
+ <entry><structfield>confelement</structfield></entry>
+ <entry><type>bool[]</type></entry>
+ <entry></entry>
+ <entry>
+ If a foreign key, list of booleans expressing which columns
+ are array <literal>ELEMENT</literal> columns; see
+ <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for details
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>conpfeqop</structfield></entry>
<entry><type>oid[]</type></entry>
<entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b05a9c2150..c1c847bc7e 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -881,7 +881,112 @@ CREATE TABLE order_items (
<xref linkend="sql-createtable">.
</para>
</sect2>
-
+
+ <sect2 id="ddl-constraints-element-fk">
+ <title>Array ELEMENT Foreign Keys</title>
+
+ <indexterm>
+ <primary>ELEMENT foreign key</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>Array ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>referential integrity</primary>
+ </indexterm>
+
+ <para>
+ Another option you have with foreign keys is to use a
+ referencing column which is an array of elements with
+ the same type (or a compatible one) as the referenced
+ column in the related table. This feature is called
+ <firstterm>array element foreign key</firstterm> and is implemented
+ in PostgreSQL with <firstterm>ELEMENT foreign key constraints</firstterm>,
+ as described in the following example:
+
+ <programlisting>
+ CREATE TABLE drivers (
+ driver_id integer PRIMARY KEY,
+ first_name text,
+ last_name text,
+ ...
+ );
+
+ CREATE TABLE races (
+ race_id integer PRIMARY KEY,
+ title text,
+ race_day DATE,
+ ...
+ final_positions integer[] <emphasis>ELEMENT REFERENCES drivers</emphasis>
+ );
+ </programlisting>
+
+ The above example uses an array (<literal>final_positions</literal>)
+ to store the results of a race: for each of its elements
+ a referential integrity check is enforced on the
+ <literal>drivers</literal> table.
+ Note that <literal>ELEMENT REFERENCES</literal> is an extension
+ of PostgreSQL and it is not included in the SQL standard.
+ </para>
+
+ <para>
+ Even though the most common use case for array <literal>ELEMENT</literal>
+ foreign keys is on a single column key, you can define an <quote>array
+ <literal>ELEMENT</literal> foreign key constraint</quote> on a group
+ of columns. As the following example shows, it must be written in table
+ constraint form:
+
+ <programlisting>
+ CREATE TABLE available_moves (
+ kind text,
+ move text,
+ description text,
+ PRIMARY KEY (kind, move)
+ );
+
+ CREATE TABLE paths (
+ description text,
+ kind text,
+ moves text[],
+ <emphasis>FOREIGN KEY (kind, ELEMENT moves) REFERENCES available_moves (kind, move)</emphasis>
+ );
+
+ INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');
+ INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');
+ INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');
+ INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');
+ INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');
+ INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');
+ INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');
+ INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');
+
+ INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');
+ INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');
+ </programlisting>
+
+ On top of standard foreign key requirements,
+ array <literal>ELEMENT</literal> foreign key constraints
+ require that the referencing column is an array of a compatible
+ type of the corresponding referenced column.
+ </para>
+
+ <para>
+ For more detailed information on array <literal>ELEMENT</literal>
+ foreign key options and special cases, please refer to the documentation
+ for <xref linkend="sql-createtable-foreign-key"> and
+ <xref linkend="sql-createtable-element-foreign-key-constraints">.
+ </para>
+
+ </sect2>
+
<sect2 id="ddl-constraints-exclusion">
<title>Exclusion Constraints</title>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b15c19d3d0..229876d735 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -65,7 +65,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> |
- REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
+ [ELEMENT] REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -76,7 +76,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
- FOREIGN KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
+ FOREIGN KEY ( [ELEMENT] <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -779,10 +779,11 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
- <varlistentry>
+ <varlistentry id="sql-createtable-foreign-key" xreflabel="FOREIGN KEY">
<term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
- <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
+ <term>
+ <literal>FOREIGN KEY ( [ELEMENT] <replaceable class="parameter">column_name</replaceable> [, ... ] )
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
[ MATCH <replaceable class="parameter">matchtype</replaceable> ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ]
@@ -806,6 +807,19 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</para>
<para>
+ In case the column name <replaceable class="parameter">column</replaceable>
+ is prepended with the <literal>ELEMENT</literal> keyword and <replaceable
+ class="parameter">column</replaceable> is an array of elements compatible
+ with the corresponding <replaceable class="parameter">refcolumn</replaceable>
+ in <replaceable class="parameter">reftable</replaceable>, an
+ array <literal>ELEMENT</literal> foreign key constraint is put in place
+ (see <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for more information).
+ Multi-column keys with more than one <literal>ELEMENT</literal> column
+ are currently not allowed.
+ </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
@@ -868,7 +882,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<para>
Delete any rows referencing the deleted row, or update the
values of the referencing column(s) to the new values of the
- referenced columns, respectively.
+ referenced columns, respectively. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -877,7 +892,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<term><literal>SET NULL</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set the referencing column(s) to null. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -889,6 +905,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
Set the referencing column(s) to their default values.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
+ Currently not supported with array <literal>ELEMENT</literal>
+ foreign keys.
</para>
</listitem>
</varlistentry>
@@ -904,6 +922,61 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
+ <varlistentry id="sql-createtable-element-foreign-key-constraints" xreflabel="ELEMENT REFERENCES">
+ <term><literal>ELEMENT REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
+
+ <listitem>
+ <para>
+ The <literal>ELEMENT REFERENCES</literal> definition specifies
+ an <quote>array <literal>ELEMENT</literal> foreign key</quote>,
+ a special kind of foreign key
+ constraint requiring the referencing column to be an array of elements
+ of the same type (or a compatible one) as the referenced column
+ in the referenced table. The value of each element of the
+ <replaceable class="parameter">refcolumn</replaceable> array
+ will be matched against some row of <replaceable
+ class="parameter">reftable</replaceable>.
+ </para>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys are an extension
+ of PostgreSQL and are not included in the SQL standard.
+ </para>
+
+ <para>
+ Even with <literal>ELEMENT</literal> foreign keys, modifications
+ in the referenced column can trigger actions to be performed on
+ the referencing array.
+ Similarly to standard foreign keys, you can specify these
+ actions using the <literal>ON DELETE</literal> and
+ <literal>ON UPDATE</literal> clauses.
+ However, only the two following actions for each clause are
+ currently allowed:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>NO ACTION</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints. This is the default action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>DEFERRABLE</literal></term>
<term><literal>NOT DEFERRABLE</literal></term>
@@ -1859,7 +1932,16 @@ CREATE TABLE cities_ab_10000_to_100000
<productname>PostgreSQL</productname> extension.
</para>
</refsect2>
-
+
+ <refsect2>
+ <title id="sql-createtable-foreign-key-arrays">Array <literal>ELEMENT</literal> Foreign Keys</title>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys and the
+ <literal>ELEMENT REFERENCES</literal> clause
+ are a <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect2>
</refsect1>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..a25ccd084c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2099,7 +2099,9 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
NULL,
NULL,
0,
- ' ',
+ false,
+ NULL,
+ ' ',
' ',
' ',
NULL, /* not an exclusion constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 027abd56b0..a2e8baba55 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1212,6 +1212,8 @@ index_constraint_create(Relation heapRelation,
NULL,
NULL,
0,
+ false,
+ NULL,
' ',
' ',
' ',
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 98bcfa08c6..0c731b1085 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1226,7 +1226,9 @@ CREATE VIEW referential_constraints AS
WHEN 'd' THEN 'SET DEFAULT'
WHEN 'r' THEN 'RESTRICT'
WHEN 'a' THEN 'NO ACTION' END
- AS character_data) AS delete_rule
+ AS character_data) AS delete_rule,
+
+ con.confiselement AS is_element
FROM (pg_namespace ncon
INNER JOIN pg_constraint con ON ncon.oid = con.connamespace
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46d3f..b96294f6a6 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -63,6 +63,8 @@ CreateConstraintEntry(const char *constraintName,
const Oid *ppEqOp,
const Oid *ffEqOp,
int foreignNKeys,
+ bool confisElement,
+ const bool *foreignElement,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
@@ -82,6 +84,7 @@ CreateConstraintEntry(const char *constraintName,
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *confelementArray;
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
@@ -132,10 +135,15 @@ CreateConstraintEntry(const char *constraintName,
fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
conffeqopArray = construct_array(fkdatums, foreignNKeys,
OIDOID, sizeof(Oid), true, 'i');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = BoolGetDatum(foreignElement[i]);
+ confelementArray = construct_array(fkdatums, foreignNKeys,
+ BOOLOID, 1, true, 'c');
}
else
{
confkeyArray = NULL;
+ confelementArray = NULL;
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
@@ -177,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+ values[Anum_pg_constraint_coniselement - 1] = BoolGetDatum(confisElement);
if (conkeyArray)
values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
@@ -188,6 +197,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_confkey - 1] = true;
+ if (confelementArray)
+ values[Anum_pg_constraint_confelement - 1] = PointerGetDatum(confelementArray);
+ else
+ nulls[Anum_pg_constraint_confelement - 1] = true;
+
if (conpfeqopArray)
values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7d9c769b06..25951c89c7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -41,6 +41,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -6995,6 +6996,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Relation pkrel;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
+ bool fkattelement[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
@@ -7082,6 +7084,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
+ MemSet(fkattelement, 0, sizeof(fkattelement));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
@@ -7094,6 +7097,39 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkattnum, fktypoid);
/*
+ * If an array ELEMENT FK, decode the content of
+ * the fk_element_attrs array.
+ */
+ if (fkconstraint->fk_is_element)
+ {
+ ListCell *l;
+ int attnum;
+ bool element_found = false;
+
+ attnum = 0;
+ foreach(l, fkconstraint->fk_element_attrs)
+ {
+ if (lfirst_int(l)) {
+
+ /*
+ * Currently, the ELEMENT flag cannot be set on more than
+ * one column.
+ */
+ if (element_found) {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("array ELEMENT foreign keys support only one ELEMENT column")));
+ }
+
+ fkattelement[attnum] = true;
+ element_found = true;
+ }
+ attnum++;
+ }
+
+ }
+
+ /*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
@@ -7140,6 +7176,22 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
old_check_ok = (fkconstraint->old_conpfeqop != NIL);
Assert(!old_check_ok || numfks == list_length(fkconstraint->old_conpfeqop));
+
+ /* Enforce array ELEMENT foreign key restrictions */
+ if (fkconstraint->fk_is_element)
+ {
+ /*
+ * Array ELEMENT foreign keys support only NO ACTION and
+ * RESTRICT actions
+ */
+ 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("array ELEMENT foreign keys only support NO ACTION and RESTRICT actions")));
+ }
for (i = 0; i < numpks; i++)
{
@@ -7156,6 +7208,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid ffeqop;
int16 eqstrategy;
Oid pfeqop_right;
+ Oid fk_element_type = InvalidOid;
/* We need several fields out of the pg_opclass entry */
cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
@@ -7189,26 +7242,51 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
eqstrategy, opcintype, opcintype, opfamily);
- /*
- * Are there equality operators that take exactly the FK type? Assume
- * we should look through any domain here.
- */
- fktyped = getBaseType(fktype);
-
- pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
- eqstrategy);
- if (OidIsValid(pfeqop))
- {
- pfeqop_right = fktyped;
- ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
- eqstrategy);
- }
- else
- {
- /* keep compiler quiet */
- pfeqop_right = InvalidOid;
- ffeqop = InvalidOid;
- }
+ if (fkattelement[i])
+ {
+ /*
+ * For every array ELEMENT FK, look if an equality operator that
+ * takes exactly the FK element type exists. Assume we should
+ * look through any domain here.
+ */
+ fk_element_type = get_base_element_type(fktype);
+ if (!OidIsValid(fk_element_type))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has type %s which is not an array type.",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktype))));
+
+ pfeqop = get_opfamily_member(opfamily, opcintype, fk_element_type,
+ eqstrategy);
+ pfeqop_right = fk_element_type;
+ ffeqop = ARRAY_EQ_OP;
+ }
+ else
+ {
+ /*
+ * Are there equality operators that take exactly the FK type?
+ * Assume we should look through any domain here.
+ */
+ fktyped = getBaseType(fktype);
+
+ pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+ eqstrategy);
+ if (OidIsValid(pfeqop))
+ {
+ pfeqop_right = fktyped;
+ ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+ eqstrategy);
+ }
+ else
+ {
+ /* keep compiler quiet */
+ pfeqop_right = InvalidOid;
+ ffeqop = InvalidOid;
+ }
+ }
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
{
@@ -7225,25 +7303,46 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid target_typeids[2];
input_typeids[0] = pktype;
- input_typeids[1] = fktype;
+ if (fkattelement[i])
+ input_typeids[1] = fk_element_type;
+ else
+ input_typeids[1] = fktype;
target_typeids[0] = opcintype;
target_typeids[1] = opcintype;
if (can_coerce_type(2, input_typeids, target_typeids,
COERCION_IMPLICIT))
{
- pfeqop = ffeqop = ppeqop;
+ pfeqop = ppeqop;
pfeqop_right = opcintype;
+ /*
+ * In case of an array ELEMENT FK, the ffeqop must be left
+ * untouched; otherwise we use the primary equality operator.
+ */
+ if (!fkattelement[i])
+ ffeqop = ppeqop;
}
}
+ /*
+ * In case of an array ELEMENT FK, make sure TYPECACHE_EQ_OPR exists
+ * for the FK element_type and it is compatible with pfeqop
+ */
+ if (fkattelement[i] && OidIsValid(pfeqop))
+ {
+ TypeCacheEntry *typentry = lookup_type_cache(fk_element_type,
+ TYPECACHE_EQ_OPR);
+ if (!OidIsValid(typentry->eq_opr)
+ || !equality_ops_are_compatible(typentry->eq_opr, pfeqop))
+ /* Error: incompatible operators */
+ pfeqop = InvalidOid;
+ }
+
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("foreign key constraint \"%s\" "
- "cannot be implemented",
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
fkconstraint->conname),
- errdetail("Key columns \"%s\" and \"%s\" "
- "are of incompatible types: %s and %s.",
+ errdetail("Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
format_type_be(fktype),
@@ -7274,8 +7373,16 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* column types to the right (foreign) operand type of the pfeqop.
* We may assume that pg_constraint.conkey is not changing.
*/
- old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
- new_fktype = fktype;
+ if (fkattelement[i])
+ {
+ old_fktype = get_base_element_type(tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid);
+ new_fktype = fk_element_type;
+ }
+ else
+ {
+ old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+ new_fktype = fktype;
+ }
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
&old_castfunc);
new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
@@ -7345,6 +7452,8 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
ppeqoperators,
ffeqoperators,
numpks,
+ fkconstraint->fk_is_element,
+ fkattelement,
fkconstraint->fk_upd_action,
fkconstraint->fk_del_action,
fkconstraint->fk_matchtype,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 45d1f515eb..c6ba754c87 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -603,6 +603,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
NULL,
NULL,
0,
+ false,
+ NULL,
' ',
' ',
' ',
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c2fc59d1aa..16479fa83b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3076,6 +3076,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
NULL,
NULL,
0,
+ false,
+ NULL,
' ',
' ',
' ',
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac8145a0..0e4361230c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2847,6 +2847,8 @@ _copyConstraint(const Constraint *from)
COPY_SCALAR_FIELD(fk_upd_action);
COPY_SCALAR_FIELD(fk_del_action);
COPY_NODE_FIELD(old_conpfeqop);
+ COPY_SCALAR_FIELD(fk_is_element);
+ COPY_NODE_FIELD(fk_element_attrs);
COPY_SCALAR_FIELD(old_pktable_oid);
COPY_SCALAR_FIELD(skip_validation);
COPY_SCALAR_FIELD(initially_valid);
@@ -2868,6 +2870,17 @@ _copyDefElem(const DefElem *from)
return newnode;
}
+static ForeignKeyColumnElem *
+_copyForeignKeyColumnElem(const ForeignKeyColumnElem *from)
+{
+ ForeignKeyColumnElem *newnode = makeNode(ForeignKeyColumnElem);
+
+ COPY_NODE_FIELD(name);
+ COPY_SCALAR_FIELD(element);
+
+ return newnode;
+}
+
static LockingClause *
_copyLockingClause(const LockingClause *from)
{
@@ -5456,6 +5469,9 @@ copyObjectImpl(const void *from)
case T_DefElem:
retval = _copyDefElem(from);
break;
+ case T_ForeignKeyColumnElem:
+ retval = _copyForeignKeyColumnElem(from);
+ break;
case T_LockingClause:
retval = _copyLockingClause(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 91d64b7331..c121a0a5cb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2579,6 +2579,8 @@ _equalConstraint(const Constraint *a, const Constraint *b)
COMPARE_SCALAR_FIELD(fk_upd_action);
COMPARE_SCALAR_FIELD(fk_del_action);
COMPARE_NODE_FIELD(old_conpfeqop);
+ COMPARE_SCALAR_FIELD(fk_is_element);
+ COMPARE_NODE_FIELD(fk_element_attrs);
COMPARE_SCALAR_FIELD(old_pktable_oid);
COMPARE_SCALAR_FIELD(skip_validation);
COMPARE_SCALAR_FIELD(initially_valid);
@@ -2599,6 +2601,16 @@ _equalDefElem(const DefElem *a, const DefElem *b)
}
static bool
+_equalForeignKeyColumnElem(const ForeignKeyColumnElem *a,
+ const ForeignKeyColumnElem *b)
+ {
+ COMPARE_NODE_FIELD(name);
+ COMPARE_SCALAR_FIELD(element);
+
+ return true;
+}
+
+static bool
_equalLockingClause(const LockingClause *a, const LockingClause *b)
{
COMPARE_NODE_FIELD(lockedRels);
@@ -3606,6 +3618,9 @@ equal(const void *a, const void *b)
case T_DefElem:
retval = _equalDefElem(a, b);
break;
+ case T_ForeignKeyColumnElem:
+ retval = _equalForeignKeyColumnElem(a, b);
+ break;
case T_LockingClause:
retval = _equalLockingClause(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a23f0bb16..3fa2b399b3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2726,6 +2726,15 @@ _outTableLikeClause(StringInfo str, const TableLikeClause *node)
}
static void
+_outForeignKeyColumnElem(StringInfo str, const ForeignKeyColumnElem *node)
+{
+ WRITE_NODE_TYPE("FOREIGNKEYCOLUMNELEM");
+
+ WRITE_NODE_FIELD(name);
+ WRITE_BOOL_FIELD(element);
+}
+
+static void
_outLockingClause(StringInfo str, const LockingClause *node)
{
WRITE_NODE_TYPE("LOCKINGCLAUSE");
@@ -3467,6 +3476,8 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_CHAR_FIELD(fk_upd_action);
WRITE_CHAR_FIELD(fk_del_action);
WRITE_NODE_FIELD(old_conpfeqop);
+ WRITE_BOOL_FIELD(fk_is_element);
+ WRITE_NODE_FIELD(fk_element_attrs);
WRITE_OID_FIELD(old_pktable_oid);
WRITE_BOOL_FIELD(skip_validation);
WRITE_BOOL_FIELD(initially_valid);
@@ -4175,6 +4186,9 @@ outNode(StringInfo str, const void *obj)
case T_TableLikeClause:
_outTableLikeClause(str, obj);
break;
+ case T_ForeignKeyColumnElem:
+ _outForeignKeyColumnElem(str, obj);
+ break;
case T_LockingClause:
_outLockingClause(str, obj);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f3998ff89..16c71ad07d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -392,7 +392,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
execute_param_clause using_clause returning_clause
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
- relation_expr_list dostmt_opt_list
+ relation_expr_list dostmt_opt_list foreignKeyColumnList
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
publication_name_list
@@ -466,7 +466,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> def_arg columnElem where_clause where_or_current_clause
a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
columnref in_expr having_clause func_table xmltable array_expr
- ExclusionWhereClause operator_def_arg
+ ExclusionWhereClause foreignKeyColumnElem operator_def_arg
%type <list> rowsfrom_item rowsfrom_list opt_col_def_list
%type <boolean> opt_ordinality
%type <list> ExclusionConstraintList ExclusionConstraintElem
@@ -622,7 +622,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
+ EACH ELEMENT ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
@@ -766,6 +766,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
/* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */
%right PRESERVE STRIP_P
+%nonassoc ELEMENT
%%
@@ -3435,6 +3436,24 @@ ColConstraintElem:
n->fk_matchtype = $4;
n->fk_upd_action = (char) ($5 >> 8);
n->fk_del_action = (char) ($5 & 0xFF);
+ n->fk_is_element = false;
+ n->skip_validation = false;
+ n->initially_valid = true;
+ $$ = (Node *)n;
+ }
+ | ELEMENT REFERENCES qualified_name opt_column_list
+ key_match key_actions
+ {
+ Constraint *n = makeNode(Constraint);
+ n->contype = CONSTR_FOREIGN;
+ n->location = @1;
+ n->pktable = $3;
+ n->fk_attrs = NIL;
+ n->pk_attrs = $4;
+ n->fk_matchtype = $5;
+ n->fk_upd_action = (char) ($6 >> 8);
+ n->fk_del_action = (char) ($6 & 0xFF);
+ n->fk_is_element = true;
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *)n;
@@ -3625,8 +3644,9 @@ ConstraintElem:
NULL, yyscanner);
$$ = (Node *)n;
}
- | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
- opt_column_list key_match key_actions ConstraintAttributeSpec
+ | FOREIGN KEY '(' foreignKeyColumnList ')' REFERENCES
+ qualified_name opt_column_list key_match key_actions
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_FOREIGN;
@@ -3649,7 +3669,31 @@ ConstraintElem:
opt_no_inherit: NO INHERIT { $$ = TRUE; }
| /* EMPTY */ { $$ = FALSE; }
;
-
+
+foreignKeyColumnList:
+ foreignKeyColumnElem
+ { $$ = list_make1($1); }
+ | foreignKeyColumnList ',' foreignKeyColumnElem
+ { $$ = lappend($1, $3); }
+ ;
+
+foreignKeyColumnElem:
+ ELEMENT ColId
+ {
+ ForeignKeyColumnElem *n = makeNode(ForeignKeyColumnElem);
+ n->name = (Node *) makeString($2);
+ n->element = true;
+ $$ = (Node *) n;
+ }
+ | ColId
+ {
+ ForeignKeyColumnElem *n = makeNode(ForeignKeyColumnElem);
+ n->name = (Node *) makeString($1);
+ n->element = false;
+ $$ = (Node *) n;
+ }
+ ;
+
opt_column_list:
'(' columnList ')' { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
@@ -14676,6 +14720,7 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | ELEMENT
| ENABLE_P
| ENCODING
| ENCRYPTED
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee5f3a3a52..128ed3b92a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -743,6 +743,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
* list of FK constraints to be processed later.
*/
constraint->fk_attrs = list_make1(makeString(column->colname));
+ constraint->fk_element_attrs =
+ list_make1_int(constraint->fk_is_element);
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -856,6 +858,33 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
break;
case CONSTR_FOREIGN:
+ /*
+ * Split the content of foreignKeyColumnList
+ * in two separate list. One list of fields
+ * and one list of boolean values.
+ */
+ {
+ ListCell *i;
+ List *old_fk_attrs = constraint->fk_attrs;
+
+ constraint->fk_attrs = NIL;
+ constraint->fk_is_element = false;
+ constraint->fk_element_attrs = NIL;
+ foreach (i, old_fk_attrs)
+ {
+ ForeignKeyColumnElem *elem =
+ (ForeignKeyColumnElem *)lfirst(i);
+
+ Assert(IsA(elem, ForeignKeyColumnElem));
+ constraint->fk_attrs =
+ lappend(constraint->fk_attrs, elem->name);
+ constraint->fk_is_element |= elem->element;
+ constraint->fk_element_attrs =
+ lappend_int(constraint->fk_element_attrs,
+ elem->element);
+ }
+ }
+
if (cxt->isforeign)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c2891e6fa1..c09277ef3a 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -115,12 +115,15 @@ typedef struct RI_ConstraintInfo
NameData conname; /* name of the FK constraint */
Oid pk_relid; /* referenced relation */
Oid fk_relid; /* referencing relation */
+ bool confiselement; /* is an array ELEMENT FK */
char confupdtype; /* foreign key's ON UPDATE action */
char confdeltype; /* foreign key's ON DELETE action */
char confmatchtype; /* foreign key's match type */
int nkeys; /* number of key columns */
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+ bool fk_element_atts[RI_MAX_NUMKEYS]; /* referencing cols is
+ * an array ELEMENT FK */
Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
@@ -204,7 +207,8 @@ static void ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype);
+ const char *rightop, Oid rightoptype,
+ bool is_array);
static void ri_add_cast_to(StringInfo buf, Oid typid);
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
static int ri_NullCheck(HeapTuple tup,
@@ -395,6 +399,7 @@ RI_FKey_check(TriggerData *trigdata)
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
+ StringInfoData countbuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
@@ -407,12 +412,24 @@ RI_FKey_check(TriggerData *trigdata)
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding FK attributes.
+ *
+ * In case of an array ELEMENT foreign key, the previous query is used
+ * to count the number of matching rows and see if every combination
+ * is actually referenced.
+ * The wrapping query is
+ * SELECT 1 WHERE 1 *
+ * (SELECT count(DISTINCT y) FROM UNNEST($1) y WHERE y IS NOT NULL)
+ * [ * ...] = (SELECT count(*) FROM (<QUERY>) z)
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
+ if (riinfo->confiselement) {
+ initStringInfo(&countbuf);
+ appendStringInfo(&countbuf, "SELECT 1 WHERE 1");
+ }
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
@@ -421,18 +438,40 @@ RI_FKey_check(TriggerData *trigdata)
quoteOneName(attname,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
+ /*
+ * In case of an array ELEMENT foreign key, we check that every
+ * DISTINCT NOT NULL value in the array is present in the PK
+ * table.
+ */
+ if (riinfo->fk_element_atts[i])
+ {
+ appendStringInfo(&countbuf,
+ " * (SELECT count(DISTINCT y) FROM UNNEST(%s) y WHERE y IS NOT NULL)",
+ paramname);
+ }
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pf_eq_oprs[i],
- paramname, fk_type);
+ paramname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = "AND";
queryoids[i] = fk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
- /* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
- &qkey, fk_rel, pk_rel, true);
+ if (riinfo->confiselement) {
+ appendStringInfo(&countbuf,
+ " = (SELECT count(*) FROM (%s) z)",
+ querybuf.data);
+
+ /* Prepare and save the plan for array ELEMENT foreign keys */
+ qplan = ri_PlanCheck(countbuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
+ }
+ else
+ /* Prepare and save the plan */
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
}
/*
@@ -559,7 +598,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pp_eq_oprs[i],
- paramname, pk_type);
+ paramname, pk_type,
+ false);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -751,7 +791,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -974,7 +1015,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1130,7 +1172,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1309,7 +1352,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ false);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1475,7 +1519,8 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1651,7 +1696,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1817,7 +1863,8 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2008,7 +2055,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_element_atts[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2327,6 +2375,14 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* For MATCH FULL:
* (fk.keycol1 IS NOT NULL [OR ...])
*
+ * In case of an array ELEMENT column, relname is replaced with the
+ * following subquery:
+ *
+ * SELECT unnest("keycol1") k1, "keycol1" ak1 [, ...]
+ * FROM ONLY "public"."fk"
+ *
+ * where all the columns are renamed in order to prevent name collisions.
+ *
* We attach COLLATE clauses to the operators when comparing columns
* that have different collations.
*----------
@@ -2338,15 +2394,46 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
quoteOneName(fkattname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
- appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+ if (riinfo->confiselement)
+ if (riinfo->fk_element_atts[i])
+ appendStringInfo(&querybuf, "%sfk.ak%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.k%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
quoteRelationName(pkrelname, pk_rel);
quoteRelationName(fkrelname, fk_rel);
- appendStringInfo(&querybuf,
- " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
- fkrelname, pkrelname);
+
+ if (riinfo->confiselement)
+ {
+ sep = "";
+ appendStringInfo(&querybuf,
+ " FROM (SELECT ");
+ for (i = 0; i < riinfo->nkeys; i++)
+ {
+ quoteOneName(fkattname,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->fk_element_atts[i])
+ appendStringInfo(&querybuf, "%sunnest(%s) k%d, %s ak%d",
+ sep, fkattname, i + 1, fkattname, i + 1);
+ else
+ appendStringInfo(&querybuf, "%s%s k%d", sep, fkattname,
+ i + 1);
+ sep = ", ";
+ }
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s) fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
+ }
+ else
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
strcpy(pkattname, "pk.");
strcpy(fkattname, "fk.");
@@ -2360,12 +2447,16 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
quoteOneName(pkattname + 3,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
- quoteOneName(fkattname + 3,
+ if (riinfo->confiselement)
+ sprintf(fkattname + 3, "k%d", i + 1);
+ else
+ quoteOneName(fkattname + 3,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
ri_GenerateQual(&querybuf, sep,
pkattname, pk_type,
riinfo->pf_eq_oprs[i],
- fkattname, fk_type);
+ fkattname, fk_type,
+ false);
if (pk_coll != fk_coll)
ri_GenerateQualCollation(&querybuf, pk_coll);
sep = "AND";
@@ -2381,7 +2472,10 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
sep = "";
for (i = 0; i < riinfo->nkeys; i++)
{
- quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->confiselement)
+ sprintf(fkattname, "k%d", i + 1);
+ else
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%sfk.%s IS NOT NULL",
sep, fkattname);
@@ -2570,7 +2664,8 @@ ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype)
+ const char *rightop, Oid rightoptype,
+ bool is_array)
{
HeapTuple opertup;
Form_pg_operator operform;
@@ -2591,9 +2686,23 @@ 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);
+ /*
+ * If rightoptype is an array of leftoptype check equality using ANY().
+ * Needed for array support in foreign keys.
+ */
+ if (is_array)
+ {
+ 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);
}
@@ -2841,6 +2950,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
riinfo->confupdtype = conForm->confupdtype;
riinfo->confdeltype = conForm->confdeltype;
riinfo->confmatchtype = conForm->confmatchtype;
+ riinfo->confiselement = conForm->confiselement;
/*
* We expect the arrays to be 1-D arrays of the right types; verify that.
@@ -2878,6 +2988,23 @@ ri_LoadConstraintInfo(Oid constraintOid)
if ((Pointer) arr != DatumGetPointer(adatum))
pfree(arr); /* free de-toasted copy, if any */
+ adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confelement, &isNull);
+ if (isNull)
+ elog(ERROR, "null confelement for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys != riinfo->nkeys ||
+ numkeys > RI_MAX_NUMKEYS ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != BOOLOID)
+ elog(ERROR, "confelement is not a 1-D boolean array");
+ memcpy(riinfo->fk_element_atts, ARR_DATA_PTR(arr),
+ numkeys * sizeof(int16));
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
adatum = SysCacheGetAttr(CONSTROID, tup,
Anum_pg_constraint_conpfeqop, &isNull);
if (isNull)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 18d9e27d1e..6f0368a523 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -314,6 +314,9 @@ static char *pg_get_viewdef_worker(Oid viewoid,
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
+static void decompile_fk_column_index_array(Datum column_index_array,
+ Datum element_array,
+ Oid relId, StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
@@ -1876,6 +1879,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
case CONSTRAINT_FOREIGN:
{
Datum val;
+ Datum element;
bool isnull;
const char *string;
@@ -1888,11 +1892,20 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
-
- decompile_column_index_array(val, conForm->conrelid, &buf);
+ element = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confelement,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null confelement for constraint %u",
+ constraintId);
+
+ decompile_fk_column_index_array(val, element,
+ conForm->conrelid, &buf);
+
+ appendStringInfo(&buf, ") REFERENCES ");
/* add foreign relation name */
- appendStringInfo(&buf, ") REFERENCES %s(",
+ appendStringInfo(&buf, "%s(",
generate_relation_name(conForm->confrelid,
NIL));
@@ -2179,6 +2192,54 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
}
+/*
+ * Convert an int16[] Datum and an bool[] Datum into a comma-separated
+ * list of column names for the indicated relation prefixed by
+ * an optional ELEMENT keyword; append the list to buf.
+ *
+ * The two arrays must have the same cardinality.
+ */
+static void
+decompile_fk_column_index_array(Datum column_index_array,
+ Datum element_array,
+ Oid relId, StringInfo buf)
+{
+ Datum *keys;
+ int nKeys;
+ Datum *bools;
+ int nBools;
+ int j;
+
+ /* Extract data from array of int16 */
+ deconstruct_array(DatumGetArrayTypeP(column_index_array),
+ INT2OID, 2, true, 's',
+ &keys, NULL, &nKeys);
+
+ /* Extract data from array of bool */
+ deconstruct_array(DatumGetArrayTypeP(element_array),
+ BOOLOID, 1, true, 'c',
+ &bools, NULL, &nBools);
+
+ if (nKeys != nBools)
+ elog(ERROR, "wrong confelement cardinality");
+
+ for (j = 0; j < nKeys; j++)
+ {
+ char *colName;
+ char *element;
+
+ colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+ element = DatumGetBool(bools[j])?"ELEMENT ":"";
+
+ if (j == 0)
+ appendStringInfo(buf, "%s%s", element,
+ quote_identifier(colName));
+ else
+ appendStringInfo(buf, ", %s%s", element,
+ quote_identifier(colName));
+ }
+}
+
/* ----------
* get_expr - Decompile an expression tree
*
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8434..177254c217 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -90,6 +90,9 @@ CATALOG(pg_constraint,2606)
/* Has a local definition and cannot be inherited */
bool connoinherit;
+ /* true if an array ELEMENT foreign key */
+ bool confiselement;
+
#ifdef CATALOG_VARLEN /* variable-length fields start here */
/*
@@ -104,6 +107,12 @@ CATALOG(pg_constraint,2606)
int16 confkey[1];
/*
+ * If a foreign key, true if array ELEMENT foreign key for each column of
+ * the constraint
+ */
+ bool confelement[1];
+
+ /*
* If a foreign key, the OIDs of the PK = FK equality operators for each
* column of the constraint
*/
@@ -150,7 +159,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 26
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -167,14 +176,16 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_conislocal 14
#define Anum_pg_constraint_coninhcount 15
#define Anum_pg_constraint_connoinherit 16
-#define Anum_pg_constraint_conkey 17
-#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_coniselement 17
+#define Anum_pg_constraint_conkey 18
+#define Anum_pg_constraint_confkey 19
+#define Anum_pg_constraint_confelement 20
+#define Anum_pg_constraint_conpfeqop 21
+#define Anum_pg_constraint_conppeqop 22
+#define Anum_pg_constraint_conffeqop 23
+#define Anum_pg_constraint_conexclop 24
+#define Anum_pg_constraint_conbin 25
+#define Anum_pg_constraint_consrc 26
/* ----------------
* initial contents of pg_constraint
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c46897ed..4aa074f9c3 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -44,6 +44,8 @@ extern Oid CreateConstraintEntry(const char *constraintName,
const Oid *ppEqOp,
const Oid *ffEqOp,
int foreignNKeys,
+ bool confisElement,
+ const bool *foreignElement,
char foreignUpdateType,
char foreignDeleteType,
char foreignMatchType,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 01527399b8..6cff3ba4b3 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -461,6 +461,7 @@ typedef enum NodeTag
T_InferClause,
T_OnConflictClause,
T_CommonTableExpr,
+ T_ForeignKeyColumnElem,
T_RoleSpec,
T_TriggerTransition,
T_PartitionElem,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1d96169d34..62843df063 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -723,6 +723,21 @@ typedef struct DefElem
} DefElem;
/*
+ * ForeignKeyColumnElem - foreign key column (used in foreign key
+ * constraint)
+ *
+ * For a foreign key attribute, 'name' is the name of the table column to
+ * index, and element is true if it is an array ELEMENT fk.
+*/
+typedef struct ForeignKeyColumnElem
+{
+ NodeTag type;
+ Node *name; /* name of the column, or NULL */
+ bool element; /* true if an array ELEMENT foreign key */
+
+} ForeignKeyColumnElem;
+
+ /*
* LockingClause - raw representation of FOR [NO KEY] UPDATE/[KEY] SHARE
* options
*
@@ -2098,6 +2113,8 @@ typedef struct Constraint
char fk_upd_action; /* ON UPDATE action */
char fk_del_action; /* ON DELETE action */
List *old_conpfeqop; /* pg_constraint.conpfeqop of my former self */
+ bool fk_is_element; /* is array ELEMENT foreign key? */
+ List *fk_element_attrs; /* array ELEMENT FK attrs */
Oid old_pktable_oid; /* pg_constraint.confrelid of my former
* self */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..d3f4803006 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -140,6 +140,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
+PG_KEYWORD("element", ELEMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/element_foreign_key.out b/src/test/regress/expected/element_foreign_key.out
new file mode 100644
index 0000000000..bfe9ebe20f
--- /dev/null
+++ b/src/test/regress/expected/element_foreign_key.out
@@ -0,0 +1,591 @@
+-- ELEMENT FK CONSTRAINTS
+--
+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 (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fkarray"
+DETAIL: Key (ftest1)=({10,1}) is not present in table "pktableforarray".
+DROP TABLE FKTABLEFORARRAY;
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+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}', 17);
+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}', 18);
+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}', 19);
+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}}', 20);
+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, 21);
+ERROR: null value in column "ftest1" violates not-null constraint
+DETAIL: Failing row contains (null, 21).
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+ {1} | 4
+(11 rows)
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+ERROR: array ELEMENT foreign keys only support NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[] ELEMENT REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(A) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(B) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({4}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({1,2,5}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using FLOAT keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float PRIMARY KEY, ptest2 text );
+-- FAILS because equality operator are incompatible
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+ERROR: foreign key constraint "fktableforarray_ftest1_fkey" cannot be implemented
+DETAIL: Key columns "ftest1" and "ptest1" are of incompatible types: integer[] and double precision.
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Composite primary keys
+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');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, ELEMENT fid2) REFERENCES PKTABLEFORARRAY);
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(A, {A,B,C}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(B, {A}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Test ELEMENT foreign keys 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[] ELEMENT 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');
+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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2010,99)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2011,1)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[] ELEMENT REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(2) is still referenced from table "fktableforarray".
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(1) is still referenced from table "fktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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');
+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;
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, ELEMENT y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(2, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(20, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, ELEMENT y) REFERENCES DIM1(x, y);
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (ELEMENT x, ELEMENT y) REFERENCES DIM1(x, y)
+);
+ERROR: array ELEMENT foreign keys support only one ELEMENT column
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT x, ELEMENT y) REFERENCES DIM1(x, y);
+ERROR: array ELEMENT foreign keys support only one ELEMENT column
+DROP TABLE F1;
+-- Cleanup
+DROP TABLE DIM1;
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(ELEMENT x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "x2_x1_fkey"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(ELEMENT x1, x2) REFERENCES x1; -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "fk_const"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+DROP TABLE x1;
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3] ELEMENT REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{100,100,100},{NULL,NULL,20},{7,8,10}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT SLOTS) REFERENCES DIM1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 23692615f9..72674fc3f2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass element_foreign_key
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5e8b7e94c4..e56038579a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -141,6 +141,7 @@ test: amutils
test: select_views
test: portals_p2
test: foreign_key
+test: element_foreign_key
test: cluster
test: dependency
test: guc
diff --git a/src/test/regress/sql/element_foreign_key.sql b/src/test/regress/sql/element_foreign_key.sql
new file mode 100644
index 0000000000..103a25b197
--- /dev/null
+++ b/src/test/regress/sql/element_foreign_key.sql
@@ -0,0 +1,452 @@
+-- ELEMENT FK CONSTRAINTS
+--
+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 (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (ELEMENT ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 17);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 18);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 19);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 20);
+INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 21);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[] ELEMENT REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using FLOAT keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float PRIMARY KEY, ptest2 text );
+-- FAILS because equality operator are incompatible
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Composite primary keys
+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');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, ELEMENT fid2) REFERENCES PKTABLEFORARRAY);
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Test ELEMENT foreign keys 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[] ELEMENT 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');
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[] ELEMENT REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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;
+
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, ELEMENT y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+
+
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, ELEMENT y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (ELEMENT x, ELEMENT y) REFERENCES DIM1(x, y)
+);
+
+
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT x, ELEMENT y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+-- Cleanup
+DROP TABLE DIM1;
+
+
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(ELEMENT x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(ELEMENT x1, x2) REFERENCES x1; -- FAILS
+DROP TABLE x2;
+DROP TABLE x1;
+
+
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3] ELEMENT REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+DROP TABLE F1;
+
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT SLOTS) REFERENCES DIM1; -- FAILS
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (ELEMENT SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+DROP TABLE F1;
+
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[] ELEMENT REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
\ No newline at end of file
Mark Rofail wrote:
On Mon, Jun 26, 2017 at 6:44 PM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:Have you met any particular problem here? Or is it just a lot of
mechanical work?Just A LOT of mechanictal work, thankfully. The patch is now rebased and
all regress tests have passed (even the element_foreign_key). Please find
the patch below !
Great!
*What I plan to do next *
- study ri_triggers.c (src/backend/utils/adt/ri_triggers.c) since this
is where the new RI code will reside
Any news?
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I was unaware that there was a ver3 patch:
/messages/by-id/28617.1351095467@sss.pgh.pa.us
I rebased this also (rebased version attached here).
There were considerable changes in syntax between v2 and v3, and different
approaches in the implementations, so I have to restudy ri_triggers.c but
at least the old patch gave me a good idea of what's going on.
As for the *limitations *of the patch:
1. Only one "ELEMENT" column allowed in a multi-column key
- - e.g. FOREIGN KEY (c1, ELEMENT c2, ELEMENT c3) REFERENCES t1 (u1,
u2, u3) will throw an error
2. Supported actions:
- - NO ACTION
- - RESTRICT
3. The use of count(distinct y) in the SQL statements if the referencing
column is an array. Since its equality operator is different from the PK
unique index equality operator this leads to a broken statement
- regression=# create table ff (f1 float8 primary key);
CREATE TABLE
regression=# create table cc (f1 numeric references ff);
CREATE TABLE
regression=# create table cc2 (f1 numeric[], foreign key(each element
of f1) references ff);
ERROR: foreign key constraint "cc2_f1_fkey" cannot be implemented
DETAIL: Key column "f1" has element type numeric which does not have
a default btree operator class that's compatible with class "float8_ops".
4. undesirable dependency on default opclass semantics in the patch,
which is that it supposes it can use array_eq() to detect whether or not
the referencing column has changed. But I think that can be fixed without
undue pain by providing a refactored version of array_eq() that can be told
which element-comparison function to use
5. fatal performance issues. If you issue any UPDATE or DELETE against
the PK table, you get a query like this for checking to see if the RI
constraint would be violated:
SELECT 1 FROM ONLY fktable x WHERE $1 = ANY (fkcol) FOR SHARE OF x;
6. cross-type FKs are unsupported
These are the limitations I gathered from the previous mailing list:
/messages/by-id/1343842863.5162.4.camel@greygoo.devise-it.lan
I am pretty sure other limitations will arise.
I am confident that between the time the patch was implemented(2012) and
now postgres has grown considerably, the array functions are now more
robust and will help in resolving many issues.
I would like to point out that Limitation #5 is the first limitation we
should eliminate as it deems the feature unbeneficial.
I would like to thank Marco Nenciarini, Gabriele, Gianni and Tom Lane, for
their hard work in the previous patches and anyone else I forgot.
As for limitations for the anyarray @> anyelem operator's *limitations*:
1. since anyarray @< anyarray and anyarray @> anyelem have the same
symbol when a statemnt like this is executed '{AAAAAAAAAA646'}' @>
'AAAAAAAAAA646' it's mapped to anyarray @< anyarray instead of anyarray @>
anyelem
- but as Alexander pointed out
On Mon, Jun 26, 2017 at 6:44 PM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
When types are not specified explicitly, then optimizer do its best on
guessing them. Sometimes results are counterintuitive to user. But that
is not bug, it's probably a room for improvement. And I don't think this
improvement should be subject of this GSoC. Anyway, array FK code should
use explicit type cast, and then you wouldn't meet this problem.
*What I plan to do next: *
- located the SQL statements triggered at any insert or update and will
now "convert" them to use GIN. However, NO ACTION and RESTRICT are the
only actions supported right now
so that's how I will spend the next week.
Best Regards,
Mark Rofail
Attachments:
Array-ELEMENT-foreign-key-v3-REBASED-42794d6.patchtext/x-patch; charset=US-ASCII; name=Array-ELEMENT-foreign-key-v3-REBASED-42794d6.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea655a10a8..b4aefd7aa3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2324,7 +2324,17 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
- <entry><structfield>conpfeqop</structfield></entry>
+ <entry><structfield>confreftype</structfield></entry>
+ <entry><type>char[]</type></entry>
+ <entry></entry>
+ <entry>If a foreign key, the reference semantics for each column:
+ <literal>p</> = plain (simple equality),
+ <literal>e</> = each element of referencing array must have a match
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>conpfeqop</structfield></entry>
<entry><type>oid[]</type></entry>
<entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
<entry>If a foreign key, list of the equality operators for PK = FK comparisons</entry>
@@ -2369,6 +2379,12 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</table>
<para>
+ When <structfield>confreftype</structfield> indicates array-vs-scalar
+ foreign key reference semantics, the equality operators listed in
+ <structfield>conpfeqop</structfield> etc are for the array's element type.
+ </para>
+
+ <para>
In the case of an exclusion constraint, <structfield>conkey</structfield>
is only useful for constraint elements that are simple column references.
For other cases, a zero appears in <structfield>conkey</structfield>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b05a9c2150..75da196334 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -882,6 +882,111 @@ CREATE TABLE order_items (
</para>
</sect2>
+ <sect2 id="ddl-constraints-element-fk">
+ <title>Array ELEMENT Foreign Keys</title>
+
+ <indexterm>
+ <primary>ELEMENT foreign key</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>Array ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>referential integrity</primary>
+ </indexterm>
+
+ <para>
+ Another option you have with foreign keys is to use a
+ referencing column which is an array of elements with
+ the same type (or a compatible one) as the referenced
+ column in the related table. This feature is called
+ <firstterm>array element foreign key</firstterm> and is implemented
+ in PostgreSQL with <firstterm>ELEMENT foreign key constraints</firstterm>,
+ as described in the following example:
+
+<programlisting>
+CREATE TABLE drivers (
+ driver_id integer PRIMARY KEY,
+ first_name text,
+ last_name text,
+ ...
+);
+
+CREATE TABLE races (
+ race_id integer PRIMARY KEY,
+ title text,
+ race_day DATE,
+ ...
+ final_positions integer[] <emphasis>ELEMENT REFERENCES drivers</emphasis>
+);
+</programlisting>
+
+ The above example uses an array (<literal>final_positions</literal>)
+ to store the results of a race: for each of its elements
+ a referential integrity check is enforced on the
+ <literal>drivers</literal> table.
+ Note that <literal>ELEMENT REFERENCES</literal> is an extension
+ of PostgreSQL and it is not included in the SQL standard.
+ </para>
+
+ <para>
+ Even though the most common use case for array <literal>ELEMENT</literal>
+ foreign keys is on a single column key, you can define an <quote>array
+ <literal>ELEMENT</literal> foreign key constraint</quote> on a group
+ of columns. As the following example shows, it must be written in table
+ constraint form:
+
+<programlisting>
+CREATE TABLE available_moves (
+ kind text,
+ move text,
+ description text,
+ PRIMARY KEY (kind, move)
+);
+
+CREATE TABLE paths (
+ description text,
+ kind text,
+ moves text[],
+ <emphasis>FOREIGN KEY (kind, ELEMENT moves) REFERENCES available_moves (kind, move)</emphasis>
+);
+
+INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');
+INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');
+INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');
+INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');
+INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');
+INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');
+INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');
+INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');
+
+INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');
+INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');
+</programlisting>
+
+ On top of standard foreign key requirements,
+ array <literal>ELEMENT</literal> foreign key constraints
+ require that the referencing column is an array of a compatible
+ type of the corresponding referenced column.
+ </para>
+
+ <para>
+ For more detailed information on array <literal>ELEMENT</literal>
+ foreign key options and special cases, please refer to the documentation
+ for <xref linkend="sql-createtable-foreign-key"> and
+ <xref linkend="sql-createtable-element-foreign-key-constraints">.
+ </para>
+
+ </sect2>
+
<sect2 id="ddl-constraints-exclusion">
<title>Exclusion Constraints</title>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index b15c19d3d0..c3fe34888e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -65,7 +65,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> |
- REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
+ [ELEMENT] REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -76,7 +76,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
- FOREIGN KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
+ FOREIGN KEY ( [ELEMENT] <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -779,10 +779,10 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
- <varlistentry>
+ <varlistentry id="sql-createtable-foreign-key" xreflabel="FOREIGN KEY">
<term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
- <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
+ <term><literal>FOREIGN KEY ( [ELEMENT] <replaceable class="parameter">column_name</replaceable> [, ... ] )
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
[ MATCH <replaceable class="parameter">matchtype</replaceable> ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ]
@@ -806,6 +806,19 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</para>
<para>
+ In case the column name <replaceable class="parameter">column</replaceable>
+ is prepended with the <literal>ELEMENT</literal> keyword and <replaceable
+ class="parameter">column</replaceable> is an array of elements compatible
+ with the corresponding <replaceable class="parameter">refcolumn</replaceable>
+ in <replaceable class="parameter">reftable</replaceable>, an
+ array <literal>ELEMENT</literal> foreign key constraint is put in place
+ (see <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for more information).
+ Multi-column keys with more than one <literal>ELEMENT</literal> column
+ are currently not allowed.
+ </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
@@ -868,7 +881,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<para>
Delete any rows referencing the deleted row, or update the
values of the referencing column(s) to the new values of the
- referenced columns, respectively.
+ referenced columns, respectively. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -877,7 +891,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<term><literal>SET NULL</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set the referencing column(s) to null. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -889,6 +904,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
Set the referencing column(s) to their default values.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
+ Currently not supported with array <literal>ELEMENT</literal>
+ foreign keys.
</para>
</listitem>
</varlistentry>
@@ -905,6 +922,61 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</varlistentry>
<varlistentry>
+ <varlistentry id="sql-createtable-element-foreign-key-constraints" xreflabel="ELEMENT REFERENCES">
+ <term><literal>ELEMENT REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
+
+ <listitem>
+ <para>
+ The <literal>ELEMENT REFERENCES</literal> definition specifies
+ an <quote>array <literal>ELEMENT</literal> foreign key</quote>,
+ a special kind of foreign key
+ constraint requiring the referencing column to be an array of elements
+ of the same type (or a compatible one) as the referenced column
+ in the referenced table. The value of each element of the
+ <replaceable class="parameter">refcolumn</replaceable> array
+ will be matched against some row of <replaceable
+ class="parameter">reftable</replaceable>.
+ </para>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys are an extension
+ of PostgreSQL and are not included in the SQL standard.
+ </para>
+
+ <para>
+ Even with <literal>ELEMENT</literal> foreign keys, modifications
+ in the referenced column can trigger actions to be performed on
+ the referencing array.
+ Similarly to standard foreign keys, you can specify these
+ actions using the <literal>ON DELETE</literal> and
+ <literal>ON UPDATE</literal> clauses.
+ However, only the two following actions for each clause are
+ currently allowed:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>NO ACTION</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints. This is the default action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<term><literal>DEFERRABLE</literal></term>
<term><literal>NOT DEFERRABLE</literal></term>
<listitem>
@@ -1843,6 +1915,16 @@ CREATE TABLE cities_ab_10000_to_100000
</refsect2>
<refsect2>
+ <title id="sql-createtable-foreign-key-arrays">Array <literal>ELEMENT</literal> Foreign Keys</title>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys and the
+ <literal>ELEMENT REFERENCES</literal> clause
+ are a <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect2>
+
+ <refsect2>
<title><literal>PARTITION BY</> Clause</title>
<para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..3bd0b772a4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2098,6 +2098,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 027abd56b0..d95ac7d594 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1211,6 +1211,7 @@ index_constraint_create(Relation heapRelation,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46d3f..c197cec11a 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -59,6 +59,7 @@ CreateConstraintEntry(const char *constraintName,
Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const char *foreignRefType,
const Oid *pfEqOp,
const Oid *ppEqOp,
const Oid *ffEqOp,
@@ -82,6 +83,7 @@ CreateConstraintEntry(const char *constraintName,
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *confreftypeArray;
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
@@ -119,7 +121,11 @@ CreateConstraintEntry(const char *constraintName,
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = Int16GetDatum(foreignKey[i]);
confkeyArray = construct_array(fkdatums, foreignNKeys,
- INT2OID, 2, true, 's');
+ INT2OID, sizeof(int16), true, 's');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = CharGetDatum(foreignRefType[i]);
+ confreftypeArray = construct_array(fkdatums, foreignNKeys,
+ CHAROID, sizeof(char), true, 'c');
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]);
conpfeqopArray = construct_array(fkdatums, foreignNKeys,
@@ -136,6 +142,7 @@ CreateConstraintEntry(const char *constraintName,
else
{
confkeyArray = NULL;
+ confreftypeArray = NULL;
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
@@ -188,6 +195,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_confkey - 1] = true;
+ if (confreftypeArray)
+ values[Anum_pg_constraint_confreftype - 1] = PointerGetDatum(confreftypeArray);
+ else
+ nulls[Anum_pg_constraint_confreftype - 1] = true;
+
if (conpfeqopArray)
values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858ad1..dc18fd1eae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6995,6 +6995,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Relation pkrel;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
+ char fkreftypes[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
@@ -7002,10 +7003,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
int i;
+ ListCell *lc;
int numfks,
numpks;
Oid indexOid;
Oid constrOid;
+ bool has_array;
bool old_check_ok;
ObjectAddress address;
ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
@@ -7082,6 +7085,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
+ MemSet(fkreftypes, 0, sizeof(fkreftypes));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
@@ -7094,6 +7098,50 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkattnum, fktypoid);
/*
+ * Validate the reference semantics codes, too, and convert list to array
+ * format to pass to CreateConstraintEntry.
+ */
+ Assert(list_length(fkconstraint->fk_reftypes) == numfks);
+ has_array = false;
+ i = 0;
+ foreach(lc, fkconstraint->fk_reftypes)
+ {
+ char reftype = lfirst_int(lc);
+
+ switch (reftype)
+ {
+ case FKCONSTR_REF_PLAIN:
+ /* OK, nothing to do */
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ /* At most one FK column can be an array reference */
+ if (has_array)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign keys support only one array column")));
+ has_array = true;
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d", (int) reftype);
+ break;
+ }
+ fkreftypes[i] = reftype;
+ i++;
+ }
+
+ /* Array foreign keys support only NO ACTION and RESTRICT actions */
+ if (has_array)
+ {
+ 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("array foreign keys support only NO ACTION and RESTRICT actions")));
+ }
+
+ /*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
@@ -7179,6 +7227,65 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
eqstrategy = BTEqualStrategyNumber;
/*
+ * If this is an array foreign key, we must look up the operators for
+ * the array element type, not the array type itself.
+ */
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ Oid elemopclass;
+
+ /* We look through any domain here */
+ fktype = get_base_element_type(fktype);
+ if (!OidIsValid(fktype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has type %s which is not an array type.",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktypoid[i]))));
+
+ /*
+ * For the moment, we must also insist that the array's element
+ * type have a default btree opclass that is in the index's
+ * opfamily. This is necessary because ri_triggers.c relies on
+ * COUNT(DISTINCT x) on the element type, as well as on array_eq()
+ * on the array type, and we need those operations to have the
+ * same notion of equality that we're using otherwise.
+ *
+ * XXX this restriction is pretty annoying, considering the effort
+ * that's been put into the rest of the RI mechanisms to make them
+ * work with nondefault equality operators. In particular, it
+ * means that the cast-to-PK-datatype code path isn't useful for
+ * array-to-scalar references.
+ */
+ elemopclass = GetDefaultOpClass(fktype, BTREE_AM_OID);
+ if (!OidIsValid(elemopclass) ||
+ get_opclass_family(elemopclass) != opfamily)
+ {
+ /* Get the index opclass's name for the error message. */
+ char *opcname;
+
+ cla_ht = SearchSysCache1(CLAOID,
+ ObjectIdGetDatum(opclasses[i]));
+ if (!HeapTupleIsValid(cla_ht))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ opclasses[i]);
+ cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+ opcname = pstrdup(NameStr(cla_tup->opcname));
+ ReleaseSysCache(cla_ht);
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has element type %s which does not have a default btree operator class that's compatible with class \"%s\".",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktype),
+ opcname)));
+ }
+ }
+
+ /*
* There had better be a primary equality operator for the index.
* We'll use it for PK = PK comparisons.
*/
@@ -7239,14 +7346,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("foreign key constraint \"%s\" "
- "cannot be implemented",
- fkconstraint->conname),
- errdetail("Key columns \"%s\" and \"%s\" "
- "are of incompatible types: %s and %s.",
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
- format_type_be(fktype),
+ format_type_be(fktypoid[i]),
format_type_be(pktype))));
if (old_check_ok)
@@ -7275,6 +7380,13 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* We may assume that pg_constraint.conkey is not changing.
*/
old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ old_fktype = get_base_element_type(old_fktype);
+ /* this shouldn't happen ... */
+ if (!OidIsValid(old_fktype))
+ elog(ERROR, "old foreign key column is not an array");
+ }
new_fktype = fktype;
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
&old_castfunc);
@@ -7317,7 +7429,6 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
new_castfunc == old_castfunc &&
(!IsPolymorphicType(pfeqop_right) ||
new_fktype == old_fktype));
-
}
pfeqoperators[i] = pfeqop;
@@ -7341,6 +7452,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
indexOid,
RelationGetRelid(pkrel),
pkattnum,
+ fkreftypes,
pfeqoperators,
ppeqoperators,
ffeqoperators,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b502941b08..cb017cbe14 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -635,6 +635,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
@@ -1006,6 +1007,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
char fk_matchtype = FKCONSTR_MATCH_SIMPLE;
List *fk_attrs = NIL;
List *pk_attrs = NIL;
+ List *fk_reftypes = NIL;
StringInfoData buf;
int funcnum;
OldTriggerInfo *info = NULL;
@@ -1035,7 +1037,10 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
if (i % 2)
fk_attrs = lappend(fk_attrs, arg);
else
- pk_attrs = lappend(pk_attrs, arg);
+ {
+ pk_attrs = lappend(pk_attrs, arg);
+ fk_reftypes = lappend_int(fk_reftypes, FKCONSTR_REF_PLAIN);
+ }
}
/* Prepare description of constraint for use in messages */
@@ -1174,6 +1179,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
fkcon->conname = constr_name;
fkcon->fk_attrs = fk_attrs;
fkcon->pk_attrs = pk_attrs;
+ fkcon->fk_reftypes = fk_reftypes;
fkcon->fk_matchtype = fk_matchtype;
switch (info->funcoids[0])
{
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c2fc59d1aa..4f089d53a9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3075,6 +3075,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 67ac8145a0..e84c226f95 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2843,6 +2843,7 @@ _copyConstraint(const Constraint *from)
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
+ COPY_NODE_FIELD(fk_reftypes);
COPY_SCALAR_FIELD(fk_matchtype);
COPY_SCALAR_FIELD(fk_upd_action);
COPY_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 91d64b7331..f11017a711 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2575,6 +2575,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
+ COMPARE_NODE_FIELD(fk_reftypes);
COMPARE_SCALAR_FIELD(fk_matchtype);
COMPARE_SCALAR_FIELD(fk_upd_action);
COMPARE_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3a23f0bb16..070e50994c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3463,6 +3463,7 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_NODE_FIELD(pktable);
WRITE_NODE_FIELD(fk_attrs);
WRITE_NODE_FIELD(pk_attrs);
+ WRITE_NODE_FIELD(fk_reftypes);
WRITE_CHAR_FIELD(fk_matchtype);
WRITE_CHAR_FIELD(fk_upd_action);
WRITE_CHAR_FIELD(fk_del_action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0f3998ff89..6a302b243c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -126,6 +126,13 @@ typedef struct ImportQual
List *table_names;
} ImportQual;
+/* Private struct for the result of foreign_key_column_elem production */
+typedef struct FKColElem
+{
+ Node *name; /* name of the column (a String) */
+ char reftype; /* FKCONSTR_REF_xxx code */
+} FKColElem;
+
/* ConstraintAttributeSpec yields an integer bitmask of these flags: */
#define CAS_NOT_DEFERRABLE 0x01
#define CAS_DEFERRABLE 0x02
@@ -183,6 +190,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
+static void SplitFKColElems(List *fkcolelems, List **names, List **reftypes);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
@@ -233,6 +241,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
A_Indices *aind;
ResTarget *target;
struct PrivTarget *privtarget;
+ struct FKColElem *fkcolelem;
AccessPriv *accesspriv;
struct ImportQual *importqual;
InsertStmt *istmt;
@@ -358,6 +367,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <accesspriv> privilege
%type <list> privileges privilege_list
%type <privtarget> privilege_target
+%type <fkcolelem> foreign_key_column_elem
%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
%type <list> function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
%type <ival> defacl_privilege_target
@@ -392,7 +402,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
execute_param_clause using_clause returning_clause
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
- relation_expr_list dostmt_opt_list
+ relation_expr_list dostmt_opt_list foreign_key_column_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
publication_name_list
@@ -622,8 +632,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
+ EACH ELEMENT ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
@@ -3427,14 +3437,16 @@ ColConstraintElem:
| REFERENCES qualified_name opt_column_list key_match key_actions
{
Constraint *n = makeNode(Constraint);
- n->contype = CONSTR_FOREIGN;
- n->location = @1;
- n->pktable = $2;
- n->fk_attrs = NIL;
- n->pk_attrs = $3;
- n->fk_matchtype = $4;
- n->fk_upd_action = (char) ($5 >> 8);
- n->fk_del_action = (char) ($5 & 0xFF);
+ n->contype = CONSTR_FOREIGN;
+ n->location = @1;
+ n->pktable = $2;
+ /* fk_attrs will be filled in by parse analysis */
+ n->fk_attrs = NIL;
+ n->pk_attrs = $3;
+ n->fk_reftypes = list_make1_int(FKCONSTR_REF_PLAIN);
+ n->fk_matchtype = $4;
+ n->fk_upd_action = (char) ($5 >> 8);
+ n->fk_del_action = (char) ($5 & 0xFF);
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *)n;
@@ -3625,14 +3637,15 @@ ConstraintElem:
NULL, yyscanner);
$$ = (Node *)n;
}
- | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
- opt_column_list key_match key_actions ConstraintAttributeSpec
+ | FOREIGN KEY '(' foreign_key_column_list ')' REFERENCES
+ qualified_name opt_column_list key_match key_actions
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_FOREIGN;
n->location = @1;
+ SplitFKColElems($4, &n->fk_attrs, &n->fk_reftypes);
n->pktable = $7;
- n->fk_attrs = $4;
n->pk_attrs = $8;
n->fk_matchtype = $9;
n->fk_upd_action = (char) ($10 >> 8);
@@ -3665,7 +3678,30 @@ columnElem: ColId
$$ = (Node *) makeString($1);
}
;
-
+ foreign_key_column_list:
+ foreign_key_column_elem
+ { $$ = list_make1($1); }
+ | foreign_key_column_list ',' foreign_key_column_elem
+ { $$ = lappend($1, $3); }
+ ;
+
+ foreign_key_column_elem:
+ ColId
+ {
+ FKColElem *n = (FKColElem *) palloc(sizeof(FKColElem));
+ n->name = (Node *) makeString($1);
+ n->reftype = FKCONSTR_REF_PLAIN;
+ $$ = n;
+ }
+ | EACH ELEMENT OF ColId
+ {
+ FKColElem *n = (FKColElem *) palloc(sizeof(FKColElem));
+ n->name = (Node *) makeString($4);
+ n->reftype = FKCONSTR_REF_EACH_ELEMENT;
+ $$ = n;
+ }
+ ;
+
key_match: MATCH FULL
{
$$ = FKCONSTR_MATCH_FULL;
@@ -14676,6 +14712,7 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | ELEMENT
| ENABLE_P
| ENCODING
| ENCRYPTED
@@ -15793,6 +15830,23 @@ SplitColQualList(List *qualList,
*constraintList = qualList;
}
+/* Split a list of FKColElem structs into separate name and reftype lists */
+static void
+SplitFKColElems(List *fkcolelems, List **names, List **reftypes)
+{
+ ListCell *lc;
+
+ *names = NIL;
+ *reftypes = NIL;
+ foreach(lc, fkcolelems)
+ {
+ FKColElem *fkcolelem = (FKColElem *) lfirst(lc);
+
+ *names = lappend(*names, fkcolelem->name);
+ *reftypes = lappend_int(*reftypes, fkcolelem->reftype);
+ }
+}
+
/*
* Process result of ConstraintAttributeSpec, and set appropriate bool flags
* in the output command node. Pass NULL for any flags the particular
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee5f3a3a52..1542cb0fa4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -743,6 +743,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
* list of FK constraints to be processed later.
*/
constraint->fk_attrs = list_make1(makeString(column->colname));
+ /* grammar should have set fk_reftypes */
+ Assert(list_length(constraint->fk_reftypes) == 1);
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c2891e6fa1..3a25ba52f3 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -118,9 +118,11 @@ typedef struct RI_ConstraintInfo
char confupdtype; /* foreign key's ON UPDATE action */
char confdeltype; /* foreign key's ON DELETE action */
char confmatchtype; /* foreign key's match type */
+ bool has_array; /* true if any reftype is EACH_ELEMENT */
int nkeys; /* number of key columns */
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+ char fk_reftypes[RI_MAX_NUMKEYS]; /* reference semantics */
Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
@@ -204,7 +206,8 @@ static void ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype);
+ const char *rightop, Oid rightoptype,
+ char fkreftype);
static void ri_add_cast_to(StringInfo buf, Oid typid);
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
static int ri_NullCheck(HeapTuple tup,
@@ -395,6 +398,7 @@ RI_FKey_check(TriggerData *trigdata)
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
+ StringInfoData countbuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
@@ -407,12 +411,22 @@ RI_FKey_check(TriggerData *trigdata)
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding FK attributes.
+ *
+ * In case of an array ELEMENT foreign key, the previous query is used
+ * to count the number of matching rows and see if every combination
+ * is actually referenced.
+ * The wrapping query is
+ * SELECT 1 WHERE
+ * (SELECT count(DISTINCT y) FROM unnest($1) y)
+ * = (SELECT count(*) FROM (<QUERY>) z)
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
+ initStringInfo(&countbuf);
+ appendStringInfo(&countbuf, "SELECT 1 WHERE ");
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
@@ -421,18 +435,41 @@ RI_FKey_check(TriggerData *trigdata)
quoteOneName(attname,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
+
+ /*
+ * In case of an array ELEMENT foreign key, we check that each
+ * distinct non-null value in the array is present in the PK
+ * table.
+ */
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&countbuf,
+ "(SELECT pg_catalog.count(DISTINCT y) FROM pg_catalog.unnest(%s) y)",
+ paramname);
+
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pf_eq_oprs[i],
- paramname, fk_type);
+ paramname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = fk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
- /* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
- &qkey, fk_rel, pk_rel, true);
+ if (riinfo->has_array)
+ {
+ appendStringInfo(&countbuf,
+ " OPERATOR(pg_catalog.=) (SELECT pg_catalog.count(*) FROM (%s) z)",
+ querybuf.data);
+
+ /* Prepare and save the plan for array ELEMENT foreign keys */
+ qplan = ri_PlanCheck(countbuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
+ }
+ else
+ /* Prepare and save the plan */
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
}
/*
@@ -559,7 +596,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pp_eq_oprs[i],
- paramname, pk_type);
+ paramname, pk_type,
+ FKCONSTR_REF_PLAIN);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -751,7 +789,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -974,7 +1013,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1130,7 +1170,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1309,7 +1350,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1475,7 +1517,8 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1651,7 +1694,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1817,7 +1861,8 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2008,7 +2053,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2327,6 +2373,14 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* For MATCH FULL:
* (fk.keycol1 IS NOT NULL [OR ...])
*
+ * In case of an array ELEMENT column, relname is replaced with the
+ * following subquery:
+ *
+ * SELECT unnest("keycol1") k1, "keycol1" ak1 [, ...]
+ * FROM ONLY "public"."fk"
+ *
+ * where all the columns are renamed in order to prevent name collisions.
+ *
* We attach COLLATE clauses to the operators when comparing columns
* that have different collations.
*----------
@@ -2338,15 +2392,46 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
quoteOneName(fkattname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
- appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+ if (riinfo->has_array)
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%sfk.ak%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.k%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
quoteRelationName(pkrelname, pk_rel);
quoteRelationName(fkrelname, fk_rel);
- appendStringInfo(&querybuf,
- " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
- fkrelname, pkrelname);
+
+ if (riinfo->has_array)
+ {
+ sep = "";
+ appendStringInfo(&querybuf,
+ " FROM (SELECT ");
+ for (i = 0; i < riinfo->nkeys; i++)
+ {
+ quoteOneName(fkattname,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%spg_catalog.unnest(%s) k%d, %s ak%d",
+ sep, fkattname, i + 1, fkattname, i + 1);
+ else
+ appendStringInfo(&querybuf, "%s%s k%d", sep, fkattname,
+ i + 1);
+ sep = ", ";
+ }
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s) fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
+ }
+ else
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
strcpy(pkattname, "pk.");
strcpy(fkattname, "fk.");
@@ -2360,12 +2445,16 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
quoteOneName(pkattname + 3,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
- quoteOneName(fkattname + 3,
- RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname + 3, "k%d", i + 1);
+ else
+ quoteOneName(fkattname + 3,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
ri_GenerateQual(&querybuf, sep,
pkattname, pk_type,
riinfo->pf_eq_oprs[i],
- fkattname, fk_type);
+ fkattname, fk_type,
+ FKCONSTR_REF_PLAIN);
if (pk_coll != fk_coll)
ri_GenerateQualCollation(&querybuf, pk_coll);
sep = "AND";
@@ -2381,7 +2470,10 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
sep = "";
for (i = 0; i < riinfo->nkeys; i++)
{
- quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname, "k%d", i + 1);
+ else
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%sfk.%s IS NOT NULL",
sep, fkattname);
@@ -2557,25 +2649,29 @@ quoteRelationName(char *buffer, Relation rel)
/*
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
- * The idea is to append " sep leftop op rightop" to buf. The complexity
- * comes from needing to be sure that the parser will select the desired
- * operator. We always name the operator using OPERATOR(schema.op) syntax
- * (readability isn't a big priority here), so as to avoid search-path
- * uncertainties. We have to emit casts too, if either input isn't already
- * the input type of the operator; else we are at the mercy of the parser's
- * heuristics for ambiguous-operator resolution.
- */
+ * The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ *
+ * The complexity comes from needing to be sure that the parser will select
+ * the desired operator. We always name the operator using
+ * OPERATOR(schema.op) syntax (readability isn't a big priority here), so as
+ * to avoid search-path uncertainties. We have to emit casts too, if either
+ * input isn't already the input type of the operator; else we are at the
+ * mercy of the parser's heuristics for ambiguous-operator resolution.
+ */
static void
ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype)
+ const char *rightop, Oid rightoptype,
+ char fkreftype)
{
HeapTuple opertup;
Form_pg_operator operform;
char *oprname;
char *nspname;
+ Oid oprright;
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
if (!HeapTupleIsValid(opertup))
@@ -2586,14 +2682,32 @@ ri_GenerateQual(StringInfo buf,
nspname = get_namespace_name(operform->oprnamespace);
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
+ {
+ oprright = get_array_type(operform->oprright);
+ if (!OidIsValid(oprright))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find array type for data type %s",
+ format_type_be(operform->oprright))));
+ }
+ else
+ oprright = operform->oprright;
+
appendStringInfo(buf, " %s %s", sep, leftop);
if (leftoptype != operform->oprleft)
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);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ quote_identifier(nspname), oprname);
+
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfoString(buf, "ANY (");
+ appendStringInfoString(buf, rightop);
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfoChar(buf, ')');
ReleaseSysCache(opertup);
}
@@ -2801,6 +2915,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
bool isNull;
ArrayType *arr;
int numkeys;
+ int i;
/*
* On the first call initialize the hashtable
@@ -2879,6 +2994,20 @@ ri_LoadConstraintInfo(Oid constraintOid)
pfree(arr); /* free de-toasted copy, if any */
adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype, &isNull);
+ if (isNull)
+ elog(ERROR, "null confreftype for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numkeys ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "confreftype is not a 1-D char array");
+ memcpy(riinfo->fk_reftypes, ARR_DATA_PTR(arr), numkeys * sizeof(char));
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
Anum_pg_constraint_conpfeqop, &isNull);
if (isNull)
elog(ERROR, "null conpfeqop for constraint %u", constraintOid);
@@ -2921,6 +3050,24 @@ ri_LoadConstraintInfo(Oid constraintOid)
if ((Pointer) arr != DatumGetPointer(adatum))
pfree(arr); /* free de-toasted copy, if any */
+ /*
+ * Fix up some stuff for array foreign keys. We need a has_array flag
+ * indicating whether there's an array foreign key, and we want to set
+ * ff_eq_oprs[i] to array_eq() for array columns, because that's what
+ * makes sense for ri_KeysEqual, and we have no other use for ff_eq_oprs
+ * in this module. (If we did, substituting the array comparator at the
+ * call point in ri_KeysEqual might be more appropriate.)
+ */
+ riinfo->has_array = false;
+ for (i = 0; i < numkeys; i++)
+ {
+ if (riinfo->fk_reftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ riinfo->has_array = true;
+ riinfo->ff_eq_oprs[i] = ARRAY_EQ_OP;
+ }
+ }
+
ReleaseSysCache(tup);
/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 18d9e27d1e..ab433bb8d6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -314,6 +314,9 @@ static char *pg_get_viewdef_worker(Oid viewoid,
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
+static void decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
@@ -1875,7 +1878,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
{
case CONSTRAINT_FOREIGN:
{
- Datum val;
+ Datum colindexes;
+ Datum reftypes;
bool isnull;
const char *string;
@@ -1883,13 +1887,21 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, "FOREIGN KEY (");
/* Fetch and build referencing-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_conkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey,
+ &isnull);
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
+ reftypes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null confreftype for constraint %u",
+ constraintId);
- decompile_column_index_array(val, conForm->conrelid, &buf);
+ decompile_fk_column_index_array(colindexes, reftypes,
+ conForm->conrelid, &buf);
/* add foreign relation name */
appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1897,13 +1909,15 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
NIL));
/* Fetch and build referenced-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_confkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confkey,
+ &isnull);
if (isnull)
elog(ERROR, "null confkey for constraint %u",
constraintId);
- decompile_column_index_array(val, conForm->confrelid, &buf);
+ decompile_column_index_array(colindexes,
+ conForm->confrelid, &buf);
appendStringInfoChar(&buf, ')');
@@ -2178,6 +2192,66 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
}
}
+ /*
+ * Convert an int16[] Datum and a char[] Datum into a comma-separated
+ * list of column names for the indicated relation, prefixed by appropriate
+ * keywords depending on the foreign key reference semantics indicated by
+ * the char[] entries. Append the text to buf.
+ */
+ static void
+ decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf)
+ {
+ Datum *keys;
+ int nKeys;
+ Datum *reftypes;
+ int nReftypes;
+ int j;
+
+ /* Extract data from array of int16 */
+ deconstruct_array(DatumGetArrayTypeP(column_index_array),
+ INT2OID, sizeof(int16), true, 's',
+ &keys, NULL, &nKeys);
+
+ /* Extract data from array of char */
+ deconstruct_array(DatumGetArrayTypeP(fk_reftype_array),
+ CHAROID, sizeof(char), true, 'c',
+ &reftypes, NULL, &nReftypes);
+
+ if (nKeys != nReftypes)
+ elog(ERROR, "wrong confreftype cardinality");
+
+ for (j = 0; j < nKeys; j++)
+ {
+ char *colName;
+ const char *prefix;
+
+ colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+
+ switch (DatumGetChar(reftypes[j]))
+ {
+ case FKCONSTR_REF_PLAIN:
+ prefix = "";
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ prefix = "EACH ELEMENT OF ";
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d",
+ (int) DatumGetChar(reftypes[j]));
+ prefix = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (j == 0)
+ appendStringInfo(buf, "%s%s", prefix,
+ quote_identifier(colName));
+ else
+ appendStringInfo(buf, ", %s%s", prefix,
+ quote_identifier(colName));
+ }
+}
/* ----------
* get_expr - Decompile an expression tree
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8434..bea6f82404 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -104,8 +104,16 @@ CATALOG(pg_constraint,2606)
int16 confkey[1];
/*
+ * If a foreign key, the reference semantics for each column
+ */
+ char confreftype[1];
+
+ /*
* If a foreign key, the OIDs of the PK = FK equality operators for each
* column of the constraint
+ *
+ * Note: for array foreign keys, all these operators are for the array's
+ * element type.
*/
Oid conpfeqop[1];
@@ -150,7 +158,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -169,12 +177,13 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_connoinherit 16
#define Anum_pg_constraint_conkey 17
#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_confreftype 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
@@ -195,7 +204,9 @@ typedef FormData_pg_constraint *Form_pg_constraint;
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
* constants defined in parsenodes.h. Valid values for confmatchtype are
- * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h.
+ * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h. Valid values
+ * for elements of confreftype[] are the FKCONSTR_REF_xxx constants defined
+ * in parsenodes.h.
*/
#endif /* PG_CONSTRAINT_H */
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c46897ed..06f4313bae 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -40,6 +40,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const char *foreignRefType,
const Oid *pfEqOp,
const Oid *ppEqOp,
const Oid *ffEqOp,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1d96169d34..28323fc99c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2059,6 +2059,10 @@ typedef enum ConstrType /* types of constraints */
#define FKCONSTR_MATCH_PARTIAL 'p'
#define FKCONSTR_MATCH_SIMPLE 's'
+ /* Foreign key column reference semantics codes */
+ #define FKCONSTR_REF_PLAIN 'p'
+ #define FKCONSTR_REF_EACH_ELEMENT 'e'
+
typedef struct Constraint
{
NodeTag type;
@@ -2094,6 +2098,7 @@ typedef struct Constraint
RangeVar *pktable; /* Primary key table */
List *fk_attrs; /* Attributes of foreign key */
List *pk_attrs; /* Corresponding attrs in PK table */
+ List *fk_reftypes; /* Per-column reference semantics (int List) */
char fk_matchtype; /* FULL, PARTIAL, SIMPLE */
char fk_upd_action; /* ON UPDATE action */
char fk_del_action; /* ON DELETE action */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..d3f4803006 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -140,6 +140,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
+PG_KEYWORD("element", ELEMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/element_foreign_key.out b/src/test/regress/expected/element_foreign_key.out
new file mode 100644
index 0000000000..b62f53e729
--- /dev/null
+++ b/src/test/regress/expected/element_foreign_key.out
@@ -0,0 +1,590 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fkarray"
+DETAIL: Key (ftest1)=({10,1}) is not present in table "pktableforarray".
+DROP TABLE FKTABLEFORARRAY;
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+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}', 17);
+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}', 18);
+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}', 19);
+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}}', 20);
+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, 21);
+ERROR: null value in column "ftest1" violates not-null constraint
+DETAIL: Failing row contains (null, 21).
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+ {1} | 4
+(11 rows)
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(A) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(B) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({4}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({1,2,5}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+ERROR: foreign key constraint "fktableforarray_ftest1_fkey" cannot be implemented
+DETAIL: Key column "ftest1" has element type integer which does not have a default btree operator class that's compatible with class "float8_ops".
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Composite primary keys
+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');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(A, {A,B,C}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(B, {A}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2010,99)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2011,1)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[], FOREIGN KEY (EACH ELEMENT OF c) REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(2) is still referenced from table "fktableforarray".
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(1) is still referenced from table "fktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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');
+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;
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(2, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(20, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+ERROR: foreign keys support only one array column
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: foreign keys support only one array column
+DROP TABLE F1;
+-- Cleanup
+DROP TABLE DIM1;
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "x2_x1_fkey"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "fk_const"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+DROP TABLE x1;
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{100,100,100},{NULL,NULL,20},{7,8,10}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 23692615f9..3ecd258f28 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
+test: select_views portals_p2 foreign_key element_foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5e8b7e94c4..e56038579a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -141,6 +141,7 @@ test: amutils
test: select_views
test: portals_p2
test: foreign_key
+test: element_foreign_key
test: cluster
test: dependency
test: guc
diff --git a/src/test/regress/sql/element_foreign_key.sql b/src/test/regress/sql/element_foreign_key.sql
new file mode 100644
index 0000000000..8c1e4d9601
--- /dev/null
+++ b/src/test/regress/sql/element_foreign_key.sql
@@ -0,0 +1,452 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 17);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 18);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 19);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 20);
+INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 21);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Composite primary keys
+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');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[], FOREIGN KEY (EACH ELEMENT OF c) REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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;
+
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+
+
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+
+
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+-- Cleanup
+DROP TABLE DIM1;
+
+
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+DROP TABLE x2;
+DROP TABLE x1;
+
+
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+DROP TABLE F1;
+
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+DROP TABLE F1;
+
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
To make the queries fired by the RI triggers GIN indexed. We need to ‒ as
Tom Lane has previously suggested[1]/messages/by-id/28389.1351094795@sss.pgh.pa.us ‒ to replace the query
SELECT 1 FROM ONLY fktable x WHERE $1 = ANY (fkcol) FOR SHARE OF x;
with
SELECT 1 FROM ONLY fktable x WHERE ARRAY[$1] <@ fkcol FOR SHARE OF x;
but since we have @<(anyarray, anyelement) it can be improved to
SELECT 1 FROM ONLY fktable x WHERE $1 @> fkcol FOR SHARE OF x;
and the piece of code responsible for all of this is ri_GenerateQual in
ri_triggers.c.
How to accomplish that is the next step. I don't know if we should hardcode
the "@>" symbol or if we just index the fk table then ri_GenerateQual would
be able to find the operator on it's own.
*What I plan to do:*
- study how to index the fk table upon its creation. I suspect this can
be done in tablecmds.c
*Questions:*
- how can you programmatically in C index a table?
[1]: /messages/by-id/28389.1351094795@sss.pgh.pa.us
Best Regards,
Mark Rofail
Attachments:
GIN-fk-RI-code-v1.patchtext/x-patch; charset=US-ASCII; name=GIN-fk-RI-code-v1.patchDownload
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3a25ba52f3..0045f64c9e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2650,7 +2650,7 @@ quoteRelationName(char *buffer, Relation rel)
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
* The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
- * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop <@ rightop" to buf.
*
* The complexity comes from needing to be sure that the parser will select
* the desired operator. We always name the operator using
@@ -2697,17 +2697,10 @@ ri_GenerateQual(StringInfo buf,
appendStringInfo(buf, " %s %s", sep, leftop);
if (leftoptype != operform->oprleft)
ri_add_cast_to(buf, operform->oprleft);
-
- appendStringInfo(buf, " OPERATOR(%s.%s) ",
- quote_identifier(nspname), oprname);
-
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoString(buf, "ANY (");
+ appendStringInfo(buf, " @> ");
appendStringInfoString(buf, rightop);
if (rightoptype != oprright)
ri_add_cast_to(buf, oprright);
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoChar(buf, ')');
ReleaseSysCache(opertup);
}
* What I am working on*
- since we want to create an index on the referencing column, I am
working on firing a 'CREATE INDEX' query programatically right after the
'CREATE TABLE' query
- The problem I ran into is how to specify my Strategy (
GinContainsElemStrategy) within the CREATE INDEX query. For
example: CREATE
INDEX ON fktable USING gin (fkcolumn array_ops)
Where does the strategy number fit?
- The patch is attached here, is the approach I took to creating an
index programmatically, correct?
Best Regard,
Mark Rofail
Attachments:
GIN-fk-RI-code-v1.1.patchtext/x-patch; charset=US-ASCII; name=GIN-fk-RI-code-v1.1.patchDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc18fd1eae..085b63aa98 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7139,6 +7139,31 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
ereport(ERROR,
(errcode(ERRCODE_INVALID_FOREIGN_KEY),
errmsg("array foreign keys support only NO ACTION and RESTRICT actions")));
+
+ IndexStmt *stmt = makeNode(IndexStmt);
+ stmt->unique = false; /* is index unique? Nope, should allow duplicates*/
+ stmt->concurrent = false; /* should this be a concurrent index build? we want
+ to lock out writes on the table until it's done. */
+ stmt->idxname = NULL; /* let the idxname be generated */
+ stmt->relation = /* relation name */;
+ stmt->accessMethod = "gin"; /* name of access method: GIN */
+ stmt->indexParams = /* column name + */"array_ops";
+ stmt->options = NULL;
+ stmt->tableSpace = NULL; /* NULL for default */
+ stmt->whereClause = NULL;
+ stmt->excludeOpNames = NIL;
+ stmt->idxcomment = NULL;
+ stmt->indexOid = InvalidOid;
+ stmt->oldNode = InvalidOid; /* relfilenode of existing storage, if any: None*/
+ stmt->primary = false; /* is index a primary key? Nope */
+ stmt->isconstraint = false; /* is it for a pkey/unique constraint? Nope */
+ stmt->deferrable = false;
+ stmt->initdeferred = false;
+ stmt->transformed = false;
+ stmt->if_not_exists = false; /* just do nothing if index already exists? Nope
+ (this shouldn't happen)*/
+
+ ATExecAddIndex(tab, rel, stmt, true, lockmode);
}
/*
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3a25ba52f3..0045f64c9e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2650,7 +2650,7 @@ quoteRelationName(char *buffer, Relation rel)
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
* The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
- * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop <@ rightop" to buf.
*
* The complexity comes from needing to be sure that the parser will select
* the desired operator. We always name the operator using
@@ -2697,17 +2697,10 @@ ri_GenerateQual(StringInfo buf,
appendStringInfo(buf, " %s %s", sep, leftop);
if (leftoptype != operform->oprleft)
ri_add_cast_to(buf, operform->oprleft);
-
- appendStringInfo(buf, " OPERATOR(%s.%s) ",
- quote_identifier(nspname), oprname);
-
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoString(buf, "ANY (");
+ appendStringInfo(buf, " @> ");
appendStringInfoString(buf, rightop);
if (rightoptype != oprright)
ri_add_cast_to(buf, oprright);
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoChar(buf, ')');
ReleaseSysCache(opertup);
}
On Sun, Jul 9, 2017 at 2:35 AM, Mark Rofail <markm.rofail@gmail.com> wrote:
* What I am working on*
- since we want to create an index on the referencing column, I am
working on firing a 'CREATE INDEX' query programatically right after
the 'CREATE TABLE' query
- The problem I ran into is how to specify my Strategy (
GinContainsElemStrategy) within the CREATE INDEX query. For
example: CREATE INDEX ON fktable USING gin (fkcolumn array_ops)
Where does the strategy number fit?
- The patch is attached here, is the approach I took to creating an
index programmatically, correct?
Could you, please, specify idea of what you're implementing in more
detail? AFACS, you're going to automatically create GIN indexes on FK
array columns. However, if we don't do this for regular columns, why
should we do for array columns? For me that sounds like a separate feature
which should be implemented for both regular and array FK columns.
Regarding your questions. If you need to create index supporting given
operator, you shouldn't take care about strategy number. Strategy number
makes sense only in opclass internals. You just need to specify opclass
which support your operator. In principle, you can find all of them in
pg_amop table. Alternatively you can just stick to GIN array_ops.
In general the approach you create index looks OK. It's OK to manually
create DDL node and execute it. As you can see, this is done in many other
places of backend code.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Sun, Jul 9, 2017 at 2:38 AM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
Could you, please, specify idea of what you're implementing in more
detail?
Ultimatley we would like an indexed scan instead of a sequential scan, so I
thought we needed to index the FK array columns first.
On Sun, Jul 9, 2017 at 1:11 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
On Sun, Jul 9, 2017 at 2:38 AM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:Could you, please, specify idea of what you're implementing in more
detail?Ultimatley we would like an indexed scan instead of a sequential scan, so
I thought we needed to index the FK array columns first.
Indeed, this is right.
But look how that works for regular FK. When you declare a FK, you
necessary need unique index on referenced column(s). However, index on
referencing columns(s) is not required. Without index on referencing
column(s), row delete in referenced table and update of referenced column
are expensive because requires sequential scan of referencing table. Users
are encouraged to index referencing column(s) to accelerate queries
produced by RI triggers. [1]
According to this, it's unclear why array FKs should behave differently.
We may document that GIN index is required to accelerate RI queries for
array FKs. And users are encouraged to manually define them.
It's also possible to define new option when index on referencing column(s)
would be created automatically. But I think this option should work the
same way for regular FKs and array FKs.
1.
https://www.postgresql.org/docs/current/static/ddl-constraints.html#DDL-CONSTRAINTS-FK
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Sun, Jul 9, 2017 at 7:42 PM, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
We may document that GIN index is required to accelerate RI queries for
array FKs. And users are encouraged to manually define them.
It's also possible to define new option when index on referencing
column(s) would be created automatically. But I think this option should
work the same way for regular FKs and array FKs.
I just thought because GIN index is suited for composite elements, it would
be appropriate for array FKs.
So we should leave it to the user ? I think tht would be fine too.
*What I did *
- now the RI checks utilise the @>(anyarray, anyelement)
- however there's a small problem:
operator does not exist: integer[] @> smallint
I assume that external casting would be required here. But how can I
downcast smallint to integer or interger to numeric automatically ?
*What I plan to do*
- work on the above mentioned buy/limitation
- otherwise, I think this concludes limitation #5
fatal performance issues. If you issue any UPDATE or DELETE against the PK
table, you get a query like this for checking to see if the RI constraint
would be violated:
SELECT 1 FROM ONLY fktable x WHERE $1 = ANY (fkcol) FOR SHARE OF x;.
or is there anything remaining ?
Best Regards,
Mark Rofail
here are the modifications to ri_triggers.c
On Wed, Jul 12, 2017 at 12:26 AM, Mark Rofail <markm.rofail@gmail.com>
wrote:
Show quoted text
*What I did *
- now the RI checks utilise the @>(anyarray, anyelement)
Best Regards,
Mark Rofail
Attachments:
GIN-fk-RI-code-v1.3.patchtext/x-patch; charset=US-ASCII; name=GIN-fk-RI-code-v1.3.patchDownload
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3a25ba52f3..2d2b8e6a4f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2650,7 +2650,7 @@ quoteRelationName(char *buffer, Relation rel)
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
* The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
- * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop <@ rightop" to buf.
*
* The complexity comes from needing to be sure that the parser will select
* the desired operator. We always name the operator using
@@ -2694,21 +2694,34 @@ ri_GenerateQual(StringInfo buf,
else
oprright = operform->oprright;
- appendStringInfo(buf, " %s %s", sep, leftop);
- if (leftoptype != operform->oprleft)
- ri_add_cast_to(buf, operform->oprleft);
-
- appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT){
+ appendStringInfo(buf, " %s %s", sep, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+
+ appendStringInfo(buf, " @> ");
+
+ appendStringInfoString(buf, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+ }
+ else{
+ appendStringInfo(buf, " %s %s", sep, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
quote_identifier(nspname), oprname);
-
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoString(buf, "ANY (");
- appendStringInfoString(buf, rightop);
- if (rightoptype != oprright)
- ri_add_cast_to(buf, oprright);
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoChar(buf, ')');
+ appendStringInfoString(buf, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ }
+
ReleaseSysCache(opertup);
}
Mark Rofail wrote:
- now the RI checks utilise the @>(anyarray, anyelement)
- however there's a small problem:
operator does not exist: integer[] @> smallint
I assume that external casting would be required here. But how can I
downcast smallint to integer or interger to numeric automatically ?
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.
BTW now that we've gone through this a little further, it's starting to
look like a mistake to me to use the same @> operator for (anyarray,
anyelement) than we use for (anyarray, anyarray). I have the feeling
we'd do better by having some other operator for this purpose -- dunno,
maybe @>> or @>. ... whatever you think is reasonable and not already
in use. Unless there is some other reason to pick @> for this purpose.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.
Can you clarify this solution ? I think another solution would be external
casting
BTW now that we've gone through this a little further, it's starting to
look like a mistake to me to use the same @> operator for (anyarray,
anyelement) than we use for (anyarray, anyarray).
I agree. Changed to @>>
Best Regards,
Mark Rofail
On Wed, Jul 12, 2017 at 2:30 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.Can you clarify this solution ? I think another solution would be external
castingIf external casting is to be used. If for example the two types in
question are smallint and integer. Would a function get_common_type(Oid
leftopr, Oid rightopr) be useful ?, that given the two types return the
"common" type between the two in this case integer.
Best Regards,
Mark Rofail
Mark Rofail wrote:
On Wed, Jul 12, 2017 at 2:30 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.Can you clarify this solution ? I think another solution would be external
castingIf external casting is to be used. If for example the two types in
question are smallint and integer. Would a function get_common_type(Oid
leftopr, Oid rightopr) be useful ?, that given the two types return the
"common" type between the two in this case integer.
Do you mean adding cast decorators to the query constructed by
ri_triggers.c? That looks like an inferior solution. What problem do
you see with adding more rows to the opclass?
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.
I tried this approach by manually declaring the operator multiple of times
in pg_amop.h (src/include/catalog/pg_amop.h)
so instead of the polymorphic declaration
DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>> anyelem
*/
multiple declarations were used, for example for int4[] :
DATA(insert ( 2745 1007 20 5 s 6108 2742 0 )); /* int4[] @>> int8 */
DATA(insert ( 2745 1007 23 5 s 6108 2742 0 )); /* int4[] @>> int4 */
DATA(insert ( 2745 1007 21 5 s 6108 2742 0 )); /* int4[] @>> int2 */
DATA(insert ( 2745 1007 1700 5 s 6108 2742 0 ));/* int4[] @>> numeric */
However, make check produced:
could not create unique index "pg_amop_opr_fam_index"
Key (amopopr, amoppurpose, amopfamily)=(6108, s, 2745) is duplicated.
Am I implementing this the wrong way or do we need to look for another
approach?
Attachments:
elemOperatorV3.4.2.patchtext/x-patch; charset=US-ASCII; name=elemOperatorV3.4.2.patchDownload
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index a5238c3af5..9d6447923d 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -43,7 +44,7 @@ ginarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(array,
@@ -110,6 +111,11 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
break;
+ case GinContainsElemStrategy:
+ /* only items that match the queried element
+ are considered candidate */
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
case GinContainsStrategy:
if (nelems > 0)
*searchMode = GIN_SEARCH_MODE_DEFAULT;
@@ -171,6 +177,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +265,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 34dadd6e19..8c9eb0c676 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,117 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid element_type,
+ bool element_isnull, Oid collation, void **fn_extra)
+{
+ Oid arr_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ if (arr_type != element_type)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare different element types")));
+
+ if (element_isnull)
+ return false;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arr_type)
+ {
+ typentry = lookup_type_cache(arr_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arr_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool element_isnull = PG_ARGISNULL(1);
+ bool result;
+
+ result = array_contains_elem(array, elem, element_type,
+ element_isnull, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3a25ba52f3..9e7d66df7e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2650,7 +2650,7 @@ quoteRelationName(char *buffer, Relation rel)
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
* The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
- * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop @>> rightop" to buf.
*
* The complexity comes from needing to be sure that the parser will select
* the desired operator. We always name the operator using
@@ -2694,21 +2694,34 @@ ri_GenerateQual(StringInfo buf,
else
oprright = operform->oprright;
- appendStringInfo(buf, " %s %s", sep, leftop);
- if (leftoptype != operform->oprleft)
- ri_add_cast_to(buf, operform->oprleft);
-
- appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT){
+ appendStringInfo(buf, " %s %s", sep, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+
+ appendStringInfo(buf, " @>> ");
+
+ appendStringInfoString(buf, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+ }
+ else{
+ appendStringInfo(buf, " %s %s", sep, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
quote_identifier(nspname), oprname);
-
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoString(buf, "ANY (");
- appendStringInfoString(buf, rightop);
- if (rightoptype != oprright)
- ri_add_cast_to(buf, oprright);
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoChar(buf, ')');
+ appendStringInfoString(buf, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ }
+
ReleaseSysCache(opertup);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index f850be490a..5649ecd22d 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,7 +689,31 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
-
+// DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>> anyelem */
+DATA(insert ( 2745 1000 16 5 s 6108 2742 0 )); /* bool[] @>> bool */
+DATA(insert ( 2745 1001 17 5 s 6108 2742 0 )); /* char[] @>> char */
+DATA(insert ( 2745 1003 18 5 s 6108 2742 0 )); /* name[] @>> name */
+DATA(insert ( 2745 1231 20 5 s 6108 2742 0 )); /* numeric[] @>> int8 */
+DATA(insert ( 2745 1231 23 5 s 6108 2742 0 )); /* numeric[] @>> int4 */
+DATA(insert ( 2745 1231 21 5 s 6108 2742 0 )); /* numeric[] @>> int2 */
+DATA(insert ( 2745 1231 1700 5 s 6108 2742 0 ));/* numeric[] @>> numeric */
+DATA(insert ( 2745 1016 20 5 s 6108 2742 0 )); /* int8[] @>> int8 */
+DATA(insert ( 2745 1016 23 5 s 6108 2742 0 )); /* int8[] @>> int4 */
+DATA(insert ( 2745 1016 21 5 s 6108 2742 0 )); /* int8[] @>> int2 */
+DATA(insert ( 2745 1016 1700 5 s 6108 2742 0 ));/* int8[] @>> numeric */
+DATA(insert ( 2745 1007 20 5 s 6108 2742 0 )); /* int4[] @>> int8 */
+DATA(insert ( 2745 1007 23 5 s 6108 2742 0 )); /* int4[] @>> int4 */
+DATA(insert ( 2745 1007 21 5 s 6108 2742 0 )); /* int4[] @>> int2 */
+DATA(insert ( 2745 1007 1700 5 s 6108 2742 0 ));/* int4[] @>> numeric */
+DATA(insert ( 2745 1005 20 5 s 6108 2742 0 )); /* int2[] @>> int8 */
+DATA(insert ( 2745 1005 23 5 s 6108 2742 0 )); /* int2[] @>> int4 */
+DATA(insert ( 2745 1005 21 5 s 6108 2742 0 )); /* int2[] @>> int2 */
+DATA(insert ( 2745 1005 1700 5 s 6108 2742 0 ));/* int2[] @>> numeric */
+DATA(insert ( 2745 1021 700 5 s 6108 2742 0 )); /* float4[] @>> float4 */
+DATA(insert ( 2745 1021 701 5 s 6108 2742 0 )); /* float4[] @>> float8 */
+DATA(insert ( 2745 1022 700 5 s 6108 2742 0 )); /* float8[] @>> float4 */
+DATA(insert ( 2745 1022 701 5 s 6108 2742 0 )); /* float8[] @>> float8 */
+
/*
* btree enum_ops
*/
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ffabc2003b..8a9d616d29 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 8b33b4e0ea..01b207d363 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c730563f03..0a9eff4d04 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -737,6 +737,17 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -761,6 +772,19 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -942,6 +966,11 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -953,6 +982,15 @@ SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
101 | {} | {}
(1 row)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -971,6 +1009,14 @@ SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fcf8bd7565..25ef369951 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1811,6 +1811,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1878,7 +1879,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 25dd4e2c6d..f6fb507bdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -313,8 +313,10 @@ SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -325,11 +327,14 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
There is a generic definition for any array added as part of
https://commitfest.postgresql.org/10/708/ (it may be the reason for the
duplicate error). I am not sure what your change is but I would review the
above just in case. There is also a defect with a misleading error that is
still being triggered for UUID arrays.
Enrique
On Mon, Jul 17, 2017 at 4:25 PM Mark Rofail <markm.rofail@gmail.com> wrote:
Show quoted text
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.I tried this approach by manually declaring the operator multiple of
times in pg_amop.h (src/include/catalog/pg_amop.h)so instead of the polymorphic declaration
DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>>
anyelem */multiple declarations were used, for example for int4[] :
DATA(insert ( 2745 1007 20 5 s 6108 2742 0 )); /* int4[] @>> int8 */
DATA(insert ( 2745 1007 23 5 s 6108 2742 0 )); /* int4[] @>> int4 */
DATA(insert ( 2745 1007 21 5 s 6108 2742 0 )); /* int4[] @>> int2 */
DATA(insert ( 2745 1007 1700 5 s 6108 2742 0 ));/* int4[] @>> numeric */However, make check produced:
could not create unique index "pg_amop_opr_fam_index"
Key (amopopr, amoppurpose, amopfamily)=(6108, s, 2745) is duplicated.Am I implementing this the wrong way or do we need to look for another
approach?--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 18, 2017 at 2:24 AM, Mark Rofail <markm.rofail@gmail.com> wrote:
On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <alvherre@2ndquadrant.com
wrote:
We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.I tried this approach by manually declaring the operator multiple of
times in pg_amop.h (src/include/catalog/pg_amop.h)so instead of the polymorphic declaration
DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>>
anyelem */multiple declarations were used, for example for int4[] :
DATA(insert ( 2745 1007 20 5 s 6108 2742 0 )); /* int4[] @>> int8 */
DATA(insert ( 2745 1007 23 5 s 6108 2742 0 )); /* int4[] @>> int4 */
DATA(insert ( 2745 1007 21 5 s 6108 2742 0 )); /* int4[] @>> int2 */
DATA(insert ( 2745 1007 1700 5 s 6108 2742 0 ));/* int4[] @>> numeric */However, make check produced:
could not create unique index "pg_amop_opr_fam_index"
Key (amopopr, amoppurpose, amopfamily)=(6108, s, 2745) is duplicated.Am I implementing this the wrong way or do we need to look for another
approach?
The problem is that you need to have not only opclass entries for the
operators, but also operators themselves. I.e. separate operators for
int4[] @>> int8, int4[] @>> int4, int4[] @>> int2, int4[] @>> numeric. You
tried to add multiple pg_amop rows for single operator and consequently get
unique index violation.
Alvaro, do you think we need to define all these operators? I'm not sure.
If even we need it, I think we shouldn't do this during this GSoC. What
particular shortcomings do you see in explicit cast in RI triggers queries?
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Tue, 18 Jul 2017 at 7:43 pm, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
Show quoted text
On T upue, Jul 18, 2017 at 2:24 AM, Mark Rofail <markm.rofail@gmail.com>
wrote:On Wed, Jul 12, 2017 at 12:53 AM, Alvaro Herrera <
alvherre@2ndquadrant.com> wrote:We have one opclass for each type combination -- int4 to int2, int4 to
int4, int4 to int8, etc. You just need to add the new strategy to all
the opclasses.I tried this approach by manually declaring the operator multiple of
times in pg_amop.h (src/include/catalog/pg_amop.h)so instead of the polymorphic declaration
DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>>
anyelem */multiple declarations were used, for example for int4[] :
DATA(insert ( 2745 1007 20 5 s 6108 2742 0 )); /* int4[] @>> int8 */
DATA(insert ( 2745 1007 23 5 s 6108 2742 0 )); /* int4[] @>> int4 */
DATA(insert ( 2745 1007 21 5 s 6108 2742 0 )); /* int4[] @>> int2 */
DATA(insert ( 2745 1007 1700 5 s 6108 2742 0 ));/* int4[] @>> numeric
*/However, make check produced:
could not create unique index "pg_amop_opr_fam_index"
Key (amopopr, amoppurpose, amopfamily)=(6108, s, 2745) is duplicated.Am I implementing this the wrong way or do we need to look for another
approach?The problem is that you need to have not only opclass entries for the
operators, but also operators themselves. I.e. separate operators for
int4[] @>> int8, int4[] @>> int4, int4[] @>> int2, int4[] @>> numeric. You
tried to add multiple pg_amop rows for single operator and consequently get
unique index violation.Alvaro, do you think we need to define all these operators? I'm not
sure. If even we need it, I think
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Tue, 18 Jul 2017 at 7:43 pm, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
separate operators for int4[] @>> int8, int4[] @>> int4, int4[] @>> int2,
int4[] @>> numeric.
My only comment on the separate operators is its high maintenance. Any new
datatype introduced a corresponding operator should be created.
Mark Rofail wrote:
On Tue, 18 Jul 2017 at 7:43 pm, Alexander Korotkov <aekorotkov@gmail.com>
wrote:separate operators for int4[] @>> int8, int4[] @>> int4, int4[] @>> int2,
int4[] @>> numeric.My only comment on the separate operators is its high maintenance. Any new
datatype introduced a corresponding operator should be created.
Yes.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alexander Korotkov wrote:
The problem is that you need to have not only opclass entries for the
operators, but also operators themselves. I.e. separate operators for
int4[] @>> int8, int4[] @>> int4, int4[] @>> int2, int4[] @>> numeric. You
tried to add multiple pg_amop rows for single operator and consequently get
unique index violation.Alvaro, do you think we need to define all these operators? I'm not sure.
If even we need it, I think we shouldn't do this during this GSoC. What
particular shortcomings do you see in explicit cast in RI triggers queries?
I'm probably confused. Why did we add an operator and not a support
procedure? I think we should have added rows in pg_amproc, not
pg_amproc. I'm very tired right now so I may be speaking nonsense.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 18, 2017 at 11:14 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
Why did we add an operator and not a support
procedure?
I thought the support procedures were constant within an opclass. They
implement the mandotary function required of an opclass. I don't see why we
would need to implement new ones since they already deal with the lefthand
operand which is the refrencing coloumn and is always an array so anyarray
would suffice.
Also the support procedure don't interact with the left and right operands
simultanously. And we want to target the combinations of int4[] @>> int8,
int4[] @>> int4, int4[] @>> int2, int4[] @>> numeric.
So I think implementing operators is the way to go.
Best Regards,
Mark Rofail.
*To summarise,* the options we have to solve the limitation of the
@>(anyarray , anyelement) where it produces the following error: operator
does not exist: integer[] @> smallint
*Option 1: *Multiple Operators
Have separate operators for every combination of datatypes instead of a
single polymorphic definition (i.e int4[] @>> int8, int4[] @>> int4, int4[]
@>> int2, int4[] @>> numeric.)
Drawback: High maintenance.
*Option 2: *Explicit casting
Where we compare the datatype of the 2 operands and cast with the
appropriate datatype
Drawback: figuring out the appropriate cast may require considerable
computation
*Option 3:* Unsafe Polymorphic datatypes
This a little out there. But since @>(anyarray, anyelement) have to resolve
to the same datatype. How about defining new datatypes without this
constraint? Where we handle the datatypes ourselves? It would ve something
like @>(unsafeAnyarray, unsafeAnyelement).
Drawback: a lot of defensive programming has to be implemented to guard
against any exception.
*Another thing*
Until this is settled, another thing I have to go through is performance
testing. To provide evidence that all we did actually enhances the
performance of the RI checks. How can I go about this?
Best Regards,
Mark Rofail
On Wed, Jul 19, 2017 at 8:08 AM, Mark Rofail <markm.rofail@gmail.com> wrote:
To summarise, the options we have to solve the limitation of the @>(anyarray
, anyelement) where it produces the following error: operator does not
exist: integer[] @> smallint
Why do we have to solve that limitation?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 19, 2017 at 7:28 PM, Robert Haas <robertmhaas@gmail.com> wrote:
Why do we have to solve that limitation?
Since the regress test labled element_foreing_key fails now that I made the
RI queries utilise @(anyarray, anyelement), that means it's not functioning
as it is meant to be.
On Wed, Jul 19, 2017 at 2:29 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
On Wed, Jul 19, 2017 at 7:28 PM, Robert Haas <robertmhaas@gmail.com> wrote:
Why do we have to solve that limitation?
Since the regress test labled element_foreing_key fails now that I made the
RI queries utilise @(anyarray, anyelement), that means it's not functioning
as it is meant to be.
Well, if this is a new test introduced by the patch, you could also
just change the test. Off-hand, I'm not sure that it's very important
to make the case work where the types don't match between the
referenced table and the referencing table, which is what you seem to
be talking about here. But maybe I'm misunderstanding the situation.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Mark Rofail wrote:
On Tue, Jul 18, 2017 at 11:14 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:Why did we add an operator and not a support
procedure?I thought the support procedures were constant within an opclass.
Uhh ... I apologize but I think I was barking at the wrong tree. I was
thinking that it mattered that the opclass mechanism was able to
determine whether some array @>> some element, but that's not true: it's
the queries in ri_triggers.c, which have no idea about opclasses.
(I tihnk we would have wanted to use to opclasses in order to find out
what operator to use in the first place, if ri_triggers.c was already
using that general idea; but in reality it's already using hardcoded
operator names, so it doesn't matter.)
I'm not entirely sure what's the best way to deal with the polymorphic
problem, but on the other hand as Robert says downthread maybe we
shouldn't be solving it at this stage anyway. So let's step back a bit,
get a patch that works for the case where the types match on both sides
of the FK, then we review that patch; if all is well, we can discuss the
other problem as a stretch goal.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 19, 2017 at 10:08 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
So let's step back a bit,
get a patch that works for the case where the types match on both sides
of the FK, then we review that patch; if all is well, we can discuss the
other problem as a stretch goal.
Agreed. This should be a future improvment.
I think the next step should be testing the performnce before/after the
modifiactions.
On Wed, Jul 19, 2017 at 11:08 PM, Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:
I'm not entirely sure what's the best way to deal with the polymorphic
problem, but on the other hand as Robert says downthread maybe we
shouldn't be solving it at this stage anyway. So let's step back a bit,
get a patch that works for the case where the types match on both sides
of the FK, then we review that patch; if all is well, we can discuss the
other problem as a stretch goal.
+1
Regular FK functionality have type restrictions based on btree opfamilies
and implicit casts. Array FK should not necessary have the same type
restrictions. Also, we don't necessary need to make those restrictions as
soft as possible during this GSoC project.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Okay, so I'm trying to test the performance of Foreign Key Array for 3
scenarios: Original Patch, After My Modifications and After My
Modifications with GIN index on the referencing column.
I have attached the sql test file here. It contains about 10k row
insertions.
However, there is a bug that prevented me from testing the third scenario,
I assume there's an issue of incompatible types problem since the right
operand type is anyelement and the supporting procedures expect anyarray.
I am working on debugging it right now.
But if it comes to it, should I introduce a new opclass specifically for
anyelement or add new supporting procedures to the old opclass ? .
I have also attached the results for the first 2 scenarios, however, the
third scenario is the most important one since it's what the project is all
about.
Also, this is kind of interesting. Upon reviewing the results
SELECT 1 FROM ONLY fktable x WHERE $1 @> fkcol FOR SHARE OF x;
produces worse results than the original
SELECT 1 FROM ONLY fktable x WHERE $1 = ANY (fkcol) FOR SHARE OF x;
so personally I don't think we should leave creating a GIN index up to the
user, it should be automatically generated instead.
On Sat, Jul 22, 2017 at 5:50 PM, Mark Rofail <markm.rofail@gmail.com> wrote:
so personally I don't think we should leave creating a GIN index up to the
user, it should be automatically generated instead.
I can certainly understand why you feel that way, but trying to do
that in your patch is just going to get your patch rejected. We don't
want array foreign keys to have different behavior than regular
foreign keys, and regular foreign keys don't do this automatically.
We could change that, but I suspect it would cause us some pretty
serious problems with upgrades from older versions with the existing
behavior to newer versions with the revised behavior.
There are other problems, too. Suppose the user creates the foreign
key and then drops the associated index; then, they run pg_dump. Will
restoring the dump recreate the index? If so, then you've broken
dump/restore, because now it doesn't actually recreate the original
state of the database. You might think of fixing this by not letting
the index be dropped, but that's problematic too, because a
fairly-standard way of removing index bloat is to create a new index
with the "concurrently" flag and then drop the old one. Another
problem entirely is that the auto-generated index will need to have an
auto-generated name, and that name might happen to conflict with the
name of some other object that already exists in the database, which
doesn't initially seem like a problem because you can just generate a
different name instead; indeed, we already do such things. But the
thorny point is that you have to preserve whatever name you choose --
and the linkage to the array foreign key that caused it to be created
-- across a dump/restore cycle; otherwise you'll have cases where
conflicting names cause failures. I doubt this is a comprehensive
list of things that might go wrong; it's intended as an illustrative
list, not an exhaustive one.
This is a jumbo king-sized can of worms, and even a very experienced
contributor would likely find it extremely difficult to sort all of
the problems that would result from a change in this area.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
It certainly is, thank you for the heads up. I included a note to encourage
the user to index the referencing column instead.
On Sun, Jul 23, 2017 at 4:41 AM, Robert Haas <robertmhaas@gmail.com> wrote:
This is a jumbo king-sized can of worms, and even a very experienced
contributor would likely find it extremely difficult to sort all of
the problems that would result from a change in this area.
Best Regards,
Mark Rofail
However, there is a bug that prevented me from testing the third scenario,
I assume there's an issue of incompatible types problem since the right
operand type is anyelement and the supporting procedures expect anyarray.
I am working on debugging it right now.
I have also solved the bug that prevented me from performance testing the
New Patch with the Index in place.
Here is a summary of the results:
A- Original Patch
DELETE Average Execution time = 3.508 ms
UPDATE Average Execution time = 3.239 ms
B- New Patch
DELETE Average Execution time = 4.970 ms
UPDATE Average Execution time = 4.170 ms
C- With Index
DELETE Average Execution time = 0.169 ms
UPDATE Average Execution time = 0.147 ms
Here is the new Patch with the bug fixes and the New Patch with the Index
in place performance results.
I just want to point this out because I still can't believe the numbers. In
reference to the old patch:
The new patch without the index suffers a 41.68% slow down, while the new
patch with the index has a 95.18% speed up!
Best Regards,
Mark Rofail
Attachments:
elemOperatorV4.patchtext/x-patch; charset=US-ASCII; name=elemOperatorV4.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c3fe34888e..828e5a6c07 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -816,6 +816,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
for more information).
Multi-column keys with more than one <literal>ELEMENT</literal> column
are currently not allowed.
+
+ It is advisable to index the refrencing column using GIN index as it considerably enhances the performance.
</para>
<para>
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index a5238c3af5..416ed60b0c 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -43,7 +44,7 @@ ginarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(array,
@@ -79,7 +80,8 @@ Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
/* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ ArrayType *array;
+ Datum elem;
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -94,45 +96,64 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* only items that match the queried element
+ are considered candidate */
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+
+ elem = PG_GETARG_DATUM(0);
+ PG_RETURN_POINTER(elem);
+ }
+ else
+ {
+ array = PG_GETARG_ARRAYTYPE_P_COPY(0);
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
- *nkeys = nelems;
- *nullFlags = nulls;
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
- switch (strategy)
- {
- case GinOverlapStrategy:
- *searchMode = GIN_SEARCH_MODE_DEFAULT;
- break;
- case GinContainsStrategy:
- if (nelems > 0)
+ *nkeys = nelems;
+ *nullFlags = nulls;
+
+ switch (strategy)
+ {
+ case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else /* everything contains the empty set */
- *searchMode = GIN_SEARCH_MODE_ALL;
- break;
- case GinContainedStrategy:
- /* empty set is contained in everything */
- *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- case GinEqualStrategy:
- if (nelems > 0)
+ break;
+ case GinContainsElemStrategy:
+ /* only items that match the queried element
+ are considered candidate */
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else
+ break;
+ case GinContainsStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else /* everything contains the empty set */
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ break;
+ case GinContainedStrategy:
+ /* empty set is contained in everything */
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- default:
- elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
- strategy);
+ break;
+ case GinEqualStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else
+ *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
+ break;
+ default:
+ elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
+ strategy);
}
/* we should not free array, elems[i] points into it */
PG_RETURN_POINTER(elems);
+ }
}
/*
@@ -171,6 +192,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +280,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 34dadd6e19..8c9eb0c676 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,117 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid element_type,
+ bool element_isnull, Oid collation, void **fn_extra)
+{
+ Oid arr_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ if (arr_type != element_type)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare different element types")));
+
+ if (element_isnull)
+ return false;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arr_type)
+ {
+ typentry = lookup_type_cache(arr_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arr_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool element_isnull = PG_ARGISNULL(1);
+ bool result;
+
+ result = array_contains_elem(array, elem, element_type,
+ element_isnull, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3a25ba52f3..9e7d66df7e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2650,7 +2650,7 @@ quoteRelationName(char *buffer, Relation rel)
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
* The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
- * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop op ANY(rightop)" to buf.
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop @>> rightop" to buf.
*
* The complexity comes from needing to be sure that the parser will select
* the desired operator. We always name the operator using
@@ -2694,21 +2694,34 @@ ri_GenerateQual(StringInfo buf,
else
oprright = operform->oprright;
- appendStringInfo(buf, " %s %s", sep, leftop);
- if (leftoptype != operform->oprleft)
- ri_add_cast_to(buf, operform->oprleft);
-
- appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT){
+ appendStringInfo(buf, " %s %s", sep, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+
+ appendStringInfo(buf, " @>> ");
+
+ appendStringInfoString(buf, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+ }
+ else{
+ appendStringInfo(buf, " %s %s", sep, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
quote_identifier(nspname), oprname);
-
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoString(buf, "ANY (");
- appendStringInfoString(buf, rightop);
- if (rightoptype != oprright)
- ri_add_cast_to(buf, oprright);
- if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
- appendStringInfoChar(buf, ')');
+ appendStringInfoString(buf, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ }
+
ReleaseSysCache(opertup);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index f850be490a..55a43d0b90 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,7 +689,8 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
-
+DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>> anyelem */
+
/*
* btree enum_ops
*/
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ffabc2003b..8a9d616d29 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 8b33b4e0ea..01b207d363 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c730563f03..0a9eff4d04 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -737,6 +737,17 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -761,6 +772,19 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -942,6 +966,11 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -953,6 +982,15 @@ SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
101 | {} | {}
(1 row)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -971,6 +1009,14 @@ SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fcf8bd7565..25ef369951 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1811,6 +1811,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1878,7 +1879,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 25dd4e2c6d..f6fb507bdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -313,8 +313,10 @@ SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -325,11 +327,14 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
On 2017-07-24 23:08, Mark Rofail wrote:
Here is the new Patch with the bug fixes and the New Patch with the
Index
in place performance results.I just want to point this out because I still can't believe the
numbers. In
reference to the old patch:
The new patch without the index suffers a 41.68% slow down, while the
new
patch with the index has a 95.18% speed up!
[elemOperatorV4.patch]
This patch doesn't apply to HEAD at the moment ( e2c8100e6072936 ).
Can you have a look?
thanks,
Erik Rijkers
patching file doc/src/sgml/ref/create_table.sgml
Hunk #1 succeeded at 816 with fuzz 3.
patching file src/backend/access/gin/ginarrayproc.c
patching file src/backend/utils/adt/arrayfuncs.c
patching file src/backend/utils/adt/ri_triggers.c
Hunk #1 FAILED at 2650.
Hunk #2 FAILED at 2694.
2 out of 2 hunks FAILED -- saving rejects to file
src/backend/utils/adt/ri_triggers.c.rej
patching file src/include/catalog/pg_amop.h
patching file src/include/catalog/pg_operator.h
patching file src/include/catalog/pg_proc.h
patching file src/test/regress/expected/arrays.out
patching file src/test/regress/expected/opr_sanity.out
patching file src/test/regress/sql/arrays.sql
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 24, 2017 at 11:25 PM, Erik Rijkers <er@xs4all.nl> wrote:
This patch doesn't apply to HEAD at the moment ( e2c8100e6072936 ).
My bad, I should have mentioned that the patch is dependant on the original
patch.
Here is a *unified* patch that I just tested.
I would appreciate it if you could review it.
Best Regards,
Mark Rofail
Attachments:
Array-ELEMENT-foreign-key-v4.patchtext/x-patch; charset=US-ASCII; name=Array-ELEMENT-foreign-key-v4.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea655a10a8..0d3a4c31d2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2324,7 +2324,16 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
- <entry><structfield>conpfeqop</structfield></entry>
+ <entry><structfield>confreftype</structfield></entry>
+ <entry><type>char[]</type></entry>
+ <entry></entry>
+ <entry>If a foreign key, the reference semantics for each column:
+ <literal>p</> = plain (simple equality),
+ <literal>e</> = each element of referencing array must have a match
+ </entry>
+ </row>
+
+ <row> <entry><structfield>conpfeqop</structfield></entry>
<entry><type>oid[]</type></entry>
<entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
<entry>If a foreign key, list of the equality operators for PK = FK comparisons</entry>
@@ -2369,6 +2378,12 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</table>
<para>
+ When <structfield>confreftype</structfield> indicates array-vs-scalar
+ foreign key reference semantics, the equality operators listed in
+ <structfield>conpfeqop</structfield> etc are for the array's element type.
+ </para>
+
+ <para>
In the case of an exclusion constraint, <structfield>conkey</structfield>
is only useful for constraint elements that are simple column references.
For other cases, a zero appears in <structfield>conkey</structfield>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b05a9c2150..75da196334 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -882,6 +882,111 @@ CREATE TABLE order_items (
</para>
</sect2>
+ <sect2 id="ddl-constraints-element-fk">
+ <title>Array ELEMENT Foreign Keys</title>
+
+ <indexterm>
+ <primary>ELEMENT foreign key</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>Array ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>referential integrity</primary>
+ </indexterm>
+
+ <para>
+ Another option you have with foreign keys is to use a
+ referencing column which is an array of elements with
+ the same type (or a compatible one) as the referenced
+ column in the related table. This feature is called
+ <firstterm>array element foreign key</firstterm> and is implemented
+ in PostgreSQL with <firstterm>ELEMENT foreign key constraints</firstterm>,
+ as described in the following example:
+
+<programlisting>
+CREATE TABLE drivers (
+ driver_id integer PRIMARY KEY,
+ first_name text,
+ last_name text,
+ ...
+);
+
+CREATE TABLE races (
+ race_id integer PRIMARY KEY,
+ title text,
+ race_day DATE,
+ ...
+ final_positions integer[] <emphasis>ELEMENT REFERENCES drivers</emphasis>
+);
+</programlisting>
+
+ The above example uses an array (<literal>final_positions</literal>)
+ to store the results of a race: for each of its elements
+ a referential integrity check is enforced on the
+ <literal>drivers</literal> table.
+ Note that <literal>ELEMENT REFERENCES</literal> is an extension
+ of PostgreSQL and it is not included in the SQL standard.
+ </para>
+
+ <para>
+ Even though the most common use case for array <literal>ELEMENT</literal>
+ foreign keys is on a single column key, you can define an <quote>array
+ <literal>ELEMENT</literal> foreign key constraint</quote> on a group
+ of columns. As the following example shows, it must be written in table
+ constraint form:
+
+<programlisting>
+CREATE TABLE available_moves (
+ kind text,
+ move text,
+ description text,
+ PRIMARY KEY (kind, move)
+);
+
+CREATE TABLE paths (
+ description text,
+ kind text,
+ moves text[],
+ <emphasis>FOREIGN KEY (kind, ELEMENT moves) REFERENCES available_moves (kind, move)</emphasis>
+);
+
+INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');
+INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');
+INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');
+INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');
+INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');
+INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');
+INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');
+INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');
+
+INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');
+INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');
+</programlisting>
+
+ On top of standard foreign key requirements,
+ array <literal>ELEMENT</literal> foreign key constraints
+ require that the referencing column is an array of a compatible
+ type of the corresponding referenced column.
+ </para>
+
+ <para>
+ For more detailed information on array <literal>ELEMENT</literal>
+ foreign key options and special cases, please refer to the documentation
+ for <xref linkend="sql-createtable-foreign-key"> and
+ <xref linkend="sql-createtable-element-foreign-key-constraints">.
+ </para>
+
+ </sect2>
+
<sect2 id="ddl-constraints-exclusion">
<title>Exclusion Constraints</title>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49533..0228fbe941 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -65,7 +65,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> |
- REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
+ [ELEMENT] REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -76,7 +76,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
- FOREIGN KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
+ FOREIGN KEY ( [ELEMENT] <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -812,10 +812,10 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
- <varlistentry>
+ <varlistentry id="sql-createtable-foreign-key" xreflabel="FOREIGN KEY">
<term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
- <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
+ <term><literal>FOREIGN KEY ( [ELEMENT] <replaceable class="parameter">column_name</replaceable> [, ... ] )
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
[ MATCH <replaceable class="parameter">matchtype</replaceable> ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ]
@@ -839,6 +839,21 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</para>
<para>
+ In case the column name <replaceable class="parameter">column</replaceable>
+ is prepended with the <literal>ELEMENT</literal> keyword and <replaceable
+ class="parameter">column</replaceable> is an array of elements compatible
+ with the corresponding <replaceable class="parameter">refcolumn</replaceable>
+ in <replaceable class="parameter">reftable</replaceable>, an
+ array <literal>ELEMENT</literal> foreign key constraint is put in place
+ (see <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for more information).
+ Multi-column keys with more than one <literal>ELEMENT</literal> column
+ are currently not allowed.
+
+ It is advisable to index the refrencing column using GIN index as it considerably enhances the performance.
+ </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
@@ -901,7 +916,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<para>
Delete any rows referencing the deleted row, or update the
values of the referencing column(s) to the new values of the
- referenced columns, respectively.
+ referenced columns, respectively. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -910,7 +926,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<term><literal>SET NULL</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set the referencing column(s) to null. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -922,6 +939,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
Set the referencing column(s) to their default values.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
+ Currently not supported with array <literal>ELEMENT</literal>
+ foreign keys.
</para>
</listitem>
</varlistentry>
@@ -938,6 +957,61 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</varlistentry>
<varlistentry>
+ <varlistentry id="sql-createtable-element-foreign-key-constraints" xreflabel="ELEMENT REFERENCES">
+ <term><literal>ELEMENT REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
+
+ <listitem>
+ <para>
+ The <literal>ELEMENT REFERENCES</literal> definition specifies
+ an <quote>array <literal>ELEMENT</literal> foreign key</quote>,
+ a special kind of foreign key
+ constraint requiring the referencing column to be an array of elements
+ of the same type (or a compatible one) as the referenced column
+ in the referenced table. The value of each element of the
+ <replaceable class="parameter">refcolumn</replaceable> array
+ will be matched against some row of <replaceable
+ class="parameter">reftable</replaceable>.
+ </para>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys are an extension
+ of PostgreSQL and are not included in the SQL standard.
+ </para>
+
+ <para>
+ Even with <literal>ELEMENT</literal> foreign keys, modifications
+ in the referenced column can trigger actions to be performed on
+ the referencing array.
+ Similarly to standard foreign keys, you can specify these
+ actions using the <literal>ON DELETE</literal> and
+ <literal>ON UPDATE</literal> clauses.
+ However, only the two following actions for each clause are
+ currently allowed:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>NO ACTION</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints. This is the default action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<term><literal>DEFERRABLE</literal></term>
<term><literal>NOT DEFERRABLE</literal></term>
<listitem>
@@ -1876,6 +1950,16 @@ CREATE TABLE cities_ab_10000_to_100000
</refsect2>
<refsect2>
+ <title id="sql-createtable-foreign-key-arrays">Array <literal>ELEMENT</literal> Foreign Keys</title>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys and the
+ <literal>ELEMENT REFERENCES</literal> clause
+ are a <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect2>
+
+ <refsect2>
<title><literal>PARTITION BY</> Clause</title>
<para>
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index a5238c3af5..416ed60b0c 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -43,7 +44,7 @@ ginarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(array,
@@ -79,7 +80,8 @@ Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
/* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ ArrayType *array;
+ Datum elem;
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -94,45 +96,64 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* only items that match the queried element
+ are considered candidate */
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+
+ elem = PG_GETARG_DATUM(0);
+ PG_RETURN_POINTER(elem);
+ }
+ else
+ {
+ array = PG_GETARG_ARRAYTYPE_P_COPY(0);
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
- *nkeys = nelems;
- *nullFlags = nulls;
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
- switch (strategy)
- {
- case GinOverlapStrategy:
- *searchMode = GIN_SEARCH_MODE_DEFAULT;
- break;
- case GinContainsStrategy:
- if (nelems > 0)
+ *nkeys = nelems;
+ *nullFlags = nulls;
+
+ switch (strategy)
+ {
+ case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else /* everything contains the empty set */
- *searchMode = GIN_SEARCH_MODE_ALL;
- break;
- case GinContainedStrategy:
- /* empty set is contained in everything */
- *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- case GinEqualStrategy:
- if (nelems > 0)
+ break;
+ case GinContainsElemStrategy:
+ /* only items that match the queried element
+ are considered candidate */
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else
+ break;
+ case GinContainsStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else /* everything contains the empty set */
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ break;
+ case GinContainedStrategy:
+ /* empty set is contained in everything */
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- default:
- elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
- strategy);
+ break;
+ case GinEqualStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else
+ *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
+ break;
+ default:
+ elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
+ strategy);
}
/* we should not free array, elems[i] points into it */
PG_RETURN_POINTER(elems);
+ }
}
/*
@@ -171,6 +192,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +280,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..3bd0b772a4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2098,6 +2098,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d25b39bb54..4e0d0bdda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1211,6 +1211,7 @@ index_constraint_create(Relation heapRelation,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46d3f..c197cec11a 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -59,6 +59,7 @@ CreateConstraintEntry(const char *constraintName,
Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const char *foreignRefType,
const Oid *pfEqOp,
const Oid *ppEqOp,
const Oid *ffEqOp,
@@ -82,6 +83,7 @@ CreateConstraintEntry(const char *constraintName,
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *confreftypeArray;
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
@@ -119,7 +121,11 @@ CreateConstraintEntry(const char *constraintName,
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = Int16GetDatum(foreignKey[i]);
confkeyArray = construct_array(fkdatums, foreignNKeys,
- INT2OID, 2, true, 's');
+ INT2OID, sizeof(int16), true, 's');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = CharGetDatum(foreignRefType[i]);
+ confreftypeArray = construct_array(fkdatums, foreignNKeys,
+ CHAROID, sizeof(char), true, 'c');
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]);
conpfeqopArray = construct_array(fkdatums, foreignNKeys,
@@ -136,6 +142,7 @@ CreateConstraintEntry(const char *constraintName,
else
{
confkeyArray = NULL;
+ confreftypeArray = NULL;
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
@@ -188,6 +195,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_confkey - 1] = true;
+ if (confreftypeArray)
+ values[Anum_pg_constraint_confreftype - 1] = PointerGetDatum(confreftypeArray);
+ else
+ nulls[Anum_pg_constraint_confreftype - 1] = true;
+
if (conpfeqopArray)
values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858ad1..634a8cd97a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6995,6 +6995,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Relation pkrel;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
+ char fkreftypes[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
@@ -7002,10 +7003,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
int i;
+ ListCell *lc;
int numfks,
numpks;
Oid indexOid;
Oid constrOid;
+ bool has_array;
bool old_check_ok;
ObjectAddress address;
ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
@@ -7082,6 +7085,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
+ MemSet(fkreftypes, 0, sizeof(fkreftypes));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
@@ -7094,6 +7098,50 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkattnum, fktypoid);
/*
+ * Validate the reference semantics codes, too, and convert list to array
+ * format to pass to CreateConstraintEntry.
+ */
+ Assert(list_length(fkconstraint->fk_reftypes) == numfks);
+ has_array = false;
+ i = 0;
+ foreach(lc, fkconstraint->fk_reftypes)
+ {
+ char reftype = lfirst_int(lc);
+
+ switch (reftype)
+ {
+ case FKCONSTR_REF_PLAIN:
+ /* OK, nothing to do */
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ /* At most one FK column can be an array reference */
+ if (has_array)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign keys support only one array column")));
+ has_array = true;
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d", (int) reftype);
+ break;
+ }
+ fkreftypes[i] = reftype;
+ i++;
+ }
+
+ /* Array foreign keys support only NO ACTION and RESTRICT actions */
+ if (has_array)
+ {
+ 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("array foreign keys support only NO ACTION and RESTRICT actions")));
+ }
+
+ /*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
@@ -7179,6 +7227,65 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
eqstrategy = BTEqualStrategyNumber;
/*
+ * If this is an array foreign key, we must look up the operators for
+ * the array element type, not the array type itself.
+ */
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ Oid elemopclass;
+
+ /* We look through any domain here */
+ fktype = get_base_element_type(fktype);
+ if (!OidIsValid(fktype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has type %s which is not an array type.",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktypoid[i]))));
+
+ /*
+ * For the moment, we must also insist that the array's element
+ * type have a default btree opclass that is in the index's
+ * opfamily. This is necessary because ri_triggers.c relies on
+ * COUNT(DISTINCT x) on the element type, as well as on array_eq()
+ * on the array type, and we need those operations to have the
+ * same notion of equality that we're using otherwise.
+ *
+ * XXX this restriction is pretty annoying, considering the effort
+ * that's been put into the rest of the RI mechanisms to make them
+ * work with nondefault equality operators. In particular, it
+ * means that the cast-to-PK-datatype code path isn't useful for
+ * array-to-scalar references.
+ */
+ elemopclass = GetDefaultOpClass(fktype, BTREE_AM_OID);
+ if (!OidIsValid(elemopclass) ||
+ get_opclass_family(elemopclass) != opfamily)
+ {
+ /* Get the index opclass's name for the error message. */
+ char *opcname;
+
+ cla_ht = SearchSysCache1(CLAOID,
+ ObjectIdGetDatum(opclasses[i]));
+ if (!HeapTupleIsValid(cla_ht))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ opclasses[i]);
+ cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+ opcname = pstrdup(NameStr(cla_tup->opcname));
+ ReleaseSysCache(cla_ht);
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has element type %s which does not have a default btree operator class that's compatible with class \"%s\".",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktype),
+ opcname)));
+ }
+ }
+
+ /*
* There had better be a primary equality operator for the index.
* We'll use it for PK = PK comparisons.
*/
@@ -7239,14 +7346,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("foreign key constraint \"%s\" "
- "cannot be implemented",
- fkconstraint->conname),
- errdetail("Key columns \"%s\" and \"%s\" "
- "are of incompatible types: %s and %s.",
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
- format_type_be(fktype),
+ format_type_be(fktypoid[i]),
format_type_be(pktype))));
if (old_check_ok)
@@ -7275,6 +7380,13 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* We may assume that pg_constraint.conkey is not changing.
*/
old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ old_fktype = get_base_element_type(old_fktype);
+ /* this shouldn't happen ... */
+ if (!OidIsValid(old_fktype))
+ elog(ERROR, "old foreign key column is not an array");
+ }
new_fktype = fktype;
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
&old_castfunc);
@@ -7341,6 +7453,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
indexOid,
RelationGetRelid(pkrel),
pkattnum,
+ fkreftypes,
pfeqoperators,
ppeqoperators,
ffeqoperators,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b502941b08..cb017cbe14 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -635,6 +635,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
@@ -1006,6 +1007,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
char fk_matchtype = FKCONSTR_MATCH_SIMPLE;
List *fk_attrs = NIL;
List *pk_attrs = NIL;
+ List *fk_reftypes = NIL;
StringInfoData buf;
int funcnum;
OldTriggerInfo *info = NULL;
@@ -1035,7 +1037,10 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
if (i % 2)
fk_attrs = lappend(fk_attrs, arg);
else
- pk_attrs = lappend(pk_attrs, arg);
+ {
+ pk_attrs = lappend(pk_attrs, arg);
+ fk_reftypes = lappend_int(fk_reftypes, FKCONSTR_REF_PLAIN);
+ }
}
/* Prepare description of constraint for use in messages */
@@ -1174,6 +1179,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
fkcon->conname = constr_name;
fkcon->fk_attrs = fk_attrs;
fkcon->pk_attrs = pk_attrs;
+ fkcon->fk_reftypes = fk_reftypes;
fkcon->fk_matchtype = fk_matchtype;
switch (info->funcoids[0])
{
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c2fc59d1aa..4f089d53a9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3075,6 +3075,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0b27..01238bfd71 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2843,6 +2843,7 @@ _copyConstraint(const Constraint *from)
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
+ COPY_NODE_FIELD(fk_reftypes);
COPY_SCALAR_FIELD(fk_matchtype);
COPY_SCALAR_FIELD(fk_upd_action);
COPY_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..d35f3d1d9a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2575,6 +2575,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
+ COMPARE_NODE_FIELD(fk_reftypes);
COMPARE_SCALAR_FIELD(fk_matchtype);
COMPARE_SCALAR_FIELD(fk_upd_action);
COMPARE_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a2b0..119d100b37 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3490,6 +3490,7 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_NODE_FIELD(pktable);
WRITE_NODE_FIELD(fk_attrs);
WRITE_NODE_FIELD(pk_attrs);
+ WRITE_NODE_FIELD(fk_reftypes);
WRITE_CHAR_FIELD(fk_matchtype);
WRITE_CHAR_FIELD(fk_upd_action);
WRITE_CHAR_FIELD(fk_del_action);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9f37f1b920..163719c7a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -743,6 +743,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
* list of FK constraints to be processed later.
*/
constraint->fk_attrs = list_make1(makeString(column->colname));
+ /* grammar should have set fk_reftypes */
+ Assert(list_length(constraint->fk_reftypes) == 1);
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 34dadd6e19..8c9eb0c676 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,117 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid element_type,
+ bool element_isnull, Oid collation, void **fn_extra)
+{
+ Oid arr_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ if (arr_type != element_type)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare different element types")));
+
+ if (element_isnull)
+ return false;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arr_type)
+ {
+ typentry = lookup_type_cache(arr_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arr_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool element_isnull = PG_ARGISNULL(1);
+ bool result;
+
+ result = array_contains_elem(array, elem, element_type,
+ element_isnull, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c2891e6fa1..9e7d66df7e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -118,9 +118,11 @@ typedef struct RI_ConstraintInfo
char confupdtype; /* foreign key's ON UPDATE action */
char confdeltype; /* foreign key's ON DELETE action */
char confmatchtype; /* foreign key's match type */
+ bool has_array; /* true if any reftype is EACH_ELEMENT */
int nkeys; /* number of key columns */
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+ char fk_reftypes[RI_MAX_NUMKEYS]; /* reference semantics */
Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
@@ -204,7 +206,8 @@ static void ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype);
+ const char *rightop, Oid rightoptype,
+ char fkreftype);
static void ri_add_cast_to(StringInfo buf, Oid typid);
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
static int ri_NullCheck(HeapTuple tup,
@@ -395,6 +398,7 @@ RI_FKey_check(TriggerData *trigdata)
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
+ StringInfoData countbuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
@@ -407,12 +411,22 @@ RI_FKey_check(TriggerData *trigdata)
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding FK attributes.
+ *
+ * In case of an array ELEMENT foreign key, the previous query is used
+ * to count the number of matching rows and see if every combination
+ * is actually referenced.
+ * The wrapping query is
+ * SELECT 1 WHERE
+ * (SELECT count(DISTINCT y) FROM unnest($1) y)
+ * = (SELECT count(*) FROM (<QUERY>) z)
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
+ initStringInfo(&countbuf);
+ appendStringInfo(&countbuf, "SELECT 1 WHERE ");
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
@@ -421,18 +435,41 @@ RI_FKey_check(TriggerData *trigdata)
quoteOneName(attname,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
+
+ /*
+ * In case of an array ELEMENT foreign key, we check that each
+ * distinct non-null value in the array is present in the PK
+ * table.
+ */
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&countbuf,
+ "(SELECT pg_catalog.count(DISTINCT y) FROM pg_catalog.unnest(%s) y)",
+ paramname);
+
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pf_eq_oprs[i],
- paramname, fk_type);
+ paramname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = fk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
- /* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
- &qkey, fk_rel, pk_rel, true);
+ if (riinfo->has_array)
+ {
+ appendStringInfo(&countbuf,
+ " OPERATOR(pg_catalog.=) (SELECT pg_catalog.count(*) FROM (%s) z)",
+ querybuf.data);
+
+ /* Prepare and save the plan for array ELEMENT foreign keys */
+ qplan = ri_PlanCheck(countbuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
+ }
+ else
+ /* Prepare and save the plan */
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
}
/*
@@ -559,7 +596,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pp_eq_oprs[i],
- paramname, pk_type);
+ paramname, pk_type,
+ FKCONSTR_REF_PLAIN);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -751,7 +789,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -974,7 +1013,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1130,7 +1170,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1309,7 +1350,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1475,7 +1517,8 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1651,7 +1694,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1817,7 +1861,8 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2008,7 +2053,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2327,6 +2373,14 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* For MATCH FULL:
* (fk.keycol1 IS NOT NULL [OR ...])
*
+ * In case of an array ELEMENT column, relname is replaced with the
+ * following subquery:
+ *
+ * SELECT unnest("keycol1") k1, "keycol1" ak1 [, ...]
+ * FROM ONLY "public"."fk"
+ *
+ * where all the columns are renamed in order to prevent name collisions.
+ *
* We attach COLLATE clauses to the operators when comparing columns
* that have different collations.
*----------
@@ -2338,15 +2392,46 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
quoteOneName(fkattname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
- appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+ if (riinfo->has_array)
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%sfk.ak%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.k%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
quoteRelationName(pkrelname, pk_rel);
quoteRelationName(fkrelname, fk_rel);
- appendStringInfo(&querybuf,
- " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
- fkrelname, pkrelname);
+
+ if (riinfo->has_array)
+ {
+ sep = "";
+ appendStringInfo(&querybuf,
+ " FROM (SELECT ");
+ for (i = 0; i < riinfo->nkeys; i++)
+ {
+ quoteOneName(fkattname,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%spg_catalog.unnest(%s) k%d, %s ak%d",
+ sep, fkattname, i + 1, fkattname, i + 1);
+ else
+ appendStringInfo(&querybuf, "%s%s k%d", sep, fkattname,
+ i + 1);
+ sep = ", ";
+ }
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s) fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
+ }
+ else
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
strcpy(pkattname, "pk.");
strcpy(fkattname, "fk.");
@@ -2360,12 +2445,16 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
quoteOneName(pkattname + 3,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
- quoteOneName(fkattname + 3,
- RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname + 3, "k%d", i + 1);
+ else
+ quoteOneName(fkattname + 3,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
ri_GenerateQual(&querybuf, sep,
pkattname, pk_type,
riinfo->pf_eq_oprs[i],
- fkattname, fk_type);
+ fkattname, fk_type,
+ FKCONSTR_REF_PLAIN);
if (pk_coll != fk_coll)
ri_GenerateQualCollation(&querybuf, pk_coll);
sep = "AND";
@@ -2381,7 +2470,10 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
sep = "";
for (i = 0; i < riinfo->nkeys; i++)
{
- quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname, "k%d", i + 1);
+ else
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%sfk.%s IS NOT NULL",
sep, fkattname);
@@ -2557,25 +2649,29 @@ quoteRelationName(char *buffer, Relation rel)
/*
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
- * The idea is to append " sep leftop op rightop" to buf. The complexity
- * comes from needing to be sure that the parser will select the desired
- * operator. We always name the operator using OPERATOR(schema.op) syntax
- * (readability isn't a big priority here), so as to avoid search-path
- * uncertainties. We have to emit casts too, if either input isn't already
- * the input type of the operator; else we are at the mercy of the parser's
- * heuristics for ambiguous-operator resolution.
- */
+ * The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop @>> rightop" to buf.
+ *
+ * The complexity comes from needing to be sure that the parser will select
+ * the desired operator. We always name the operator using
+ * OPERATOR(schema.op) syntax (readability isn't a big priority here), so as
+ * to avoid search-path uncertainties. We have to emit casts too, if either
+ * input isn't already the input type of the operator; else we are at the
+ * mercy of the parser's heuristics for ambiguous-operator resolution.
+ */
static void
ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype)
+ const char *rightop, Oid rightoptype,
+ char fkreftype)
{
HeapTuple opertup;
Form_pg_operator operform;
char *oprname;
char *nspname;
+ Oid oprright;
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
if (!HeapTupleIsValid(opertup))
@@ -2586,15 +2682,46 @@ ri_GenerateQual(StringInfo buf,
nspname = get_namespace_name(operform->oprnamespace);
- appendStringInfo(buf, " %s %s", sep, leftop);
- if (leftoptype != operform->oprleft)
- 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);
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
+ {
+ oprright = get_array_type(operform->oprright);
+ if (!OidIsValid(oprright))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find array type for data type %s",
+ format_type_be(operform->oprright))));
+ }
+ else
+ oprright = operform->oprright;
+
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT){
+ appendStringInfo(buf, " %s %s", sep, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ appendStringInfo(buf, " @>> ");
+
+ appendStringInfoString(buf, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+ }
+ else{
+ appendStringInfo(buf, " %s %s", sep, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ quote_identifier(nspname), oprname);
+
+ appendStringInfoString(buf, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ }
+
ReleaseSysCache(opertup);
}
@@ -2801,6 +2928,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
bool isNull;
ArrayType *arr;
int numkeys;
+ int i;
/*
* On the first call initialize the hashtable
@@ -2879,6 +3007,20 @@ ri_LoadConstraintInfo(Oid constraintOid)
pfree(arr); /* free de-toasted copy, if any */
adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype, &isNull);
+ if (isNull)
+ elog(ERROR, "null confreftype for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numkeys ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "confreftype is not a 1-D char array");
+ memcpy(riinfo->fk_reftypes, ARR_DATA_PTR(arr), numkeys * sizeof(char));
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
Anum_pg_constraint_conpfeqop, &isNull);
if (isNull)
elog(ERROR, "null conpfeqop for constraint %u", constraintOid);
@@ -2921,6 +3063,24 @@ ri_LoadConstraintInfo(Oid constraintOid)
if ((Pointer) arr != DatumGetPointer(adatum))
pfree(arr); /* free de-toasted copy, if any */
+ /*
+ * Fix up some stuff for array foreign keys. We need a has_array flag
+ * indicating whether there's an array foreign key, and we want to set
+ * ff_eq_oprs[i] to array_eq() for array columns, because that's what
+ * makes sense for ri_KeysEqual, and we have no other use for ff_eq_oprs
+ * in this module. (If we did, substituting the array comparator at the
+ * call point in ri_KeysEqual might be more appropriate.)
+ */
+ riinfo->has_array = false;
+ for (i = 0; i < numkeys; i++)
+ {
+ if (riinfo->fk_reftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ riinfo->has_array = true;
+ riinfo->ff_eq_oprs[i] = ARRAY_EQ_OP;
+ }
+ }
+
ReleaseSysCache(tup);
/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d83377d1d8..680623534c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -314,6 +314,9 @@ static char *pg_get_viewdef_worker(Oid viewoid,
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
+static void decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
@@ -1882,7 +1885,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
{
case CONSTRAINT_FOREIGN:
{
- Datum val;
+ Datum colindexes;
+ Datum reftypes;
bool isnull;
const char *string;
@@ -1890,13 +1894,21 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, "FOREIGN KEY (");
/* Fetch and build referencing-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_conkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey,
+ &isnull);
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
+ reftypes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null confreftype for constraint %u",
+ constraintId);
- decompile_column_index_array(val, conForm->conrelid, &buf);
+ decompile_fk_column_index_array(colindexes, reftypes,
+ conForm->conrelid, &buf);
/* add foreign relation name */
appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1904,13 +1916,15 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
NIL));
/* Fetch and build referenced-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_confkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confkey,
+ &isnull);
if (isnull)
elog(ERROR, "null confkey for constraint %u",
constraintId);
- decompile_column_index_array(val, conForm->confrelid, &buf);
+ decompile_column_index_array(colindexes,
+ conForm->confrelid, &buf);
appendStringInfoChar(&buf, ')');
@@ -2185,6 +2199,66 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
}
}
+ /*
+ * Convert an int16[] Datum and a char[] Datum into a comma-separated
+ * list of column names for the indicated relation, prefixed by appropriate
+ * keywords depending on the foreign key reference semantics indicated by
+ * the char[] entries. Append the text to buf.
+ */
+ static void
+ decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf)
+ {
+ Datum *keys;
+ int nKeys;
+ Datum *reftypes;
+ int nReftypes;
+ int j;
+
+ /* Extract data from array of int16 */
+ deconstruct_array(DatumGetArrayTypeP(column_index_array),
+ INT2OID, sizeof(int16), true, 's',
+ &keys, NULL, &nKeys);
+
+ /* Extract data from array of char */
+ deconstruct_array(DatumGetArrayTypeP(fk_reftype_array),
+ CHAROID, sizeof(char), true, 'c',
+ &reftypes, NULL, &nReftypes);
+
+ if (nKeys != nReftypes)
+ elog(ERROR, "wrong confreftype cardinality");
+
+ for (j = 0; j < nKeys; j++)
+ {
+ char *colName;
+ const char *prefix;
+
+ colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+
+ switch (DatumGetChar(reftypes[j]))
+ {
+ case FKCONSTR_REF_PLAIN:
+ prefix = "";
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ prefix = "EACH ELEMENT OF ";
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d",
+ (int) DatumGetChar(reftypes[j]));
+ prefix = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (j == 0)
+ appendStringInfo(buf, "%s%s", prefix,
+ quote_identifier(colName));
+ else
+ appendStringInfo(buf, ", %s%s", prefix,
+ quote_identifier(colName));
+ }
+}
/* ----------
* get_expr - Decompile an expression tree
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index f850be490a..55a43d0b90 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,7 +689,8 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
-
+DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>> anyelem */
+
/*
* btree enum_ops
*/
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8434..bea6f82404 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -104,8 +104,16 @@ CATALOG(pg_constraint,2606)
int16 confkey[1];
/*
+ * If a foreign key, the reference semantics for each column
+ */
+ char confreftype[1];
+
+ /*
* If a foreign key, the OIDs of the PK = FK equality operators for each
* column of the constraint
+ *
+ * Note: for array foreign keys, all these operators are for the array's
+ * element type.
*/
Oid conpfeqop[1];
@@ -150,7 +158,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -169,12 +177,13 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_connoinherit 16
#define Anum_pg_constraint_conkey 17
#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_confreftype 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
@@ -195,7 +204,9 @@ typedef FormData_pg_constraint *Form_pg_constraint;
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
* constants defined in parsenodes.h. Valid values for confmatchtype are
- * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h.
+ * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h. Valid values
+ * for elements of confreftype[] are the FKCONSTR_REF_xxx constants defined
+ * in parsenodes.h.
*/
#endif /* PG_CONSTRAINT_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ffabc2003b..8a9d616d29 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 8b33b4e0ea..01b207d363 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..7662ef8018 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2067,6 +2067,10 @@ typedef enum ConstrType /* types of constraints */
#define FKCONSTR_MATCH_PARTIAL 'p'
#define FKCONSTR_MATCH_SIMPLE 's'
+ /* Foreign key column reference semantics codes */
+ #define FKCONSTR_REF_PLAIN 'p'
+ #define FKCONSTR_REF_EACH_ELEMENT 'e'
+
typedef struct Constraint
{
NodeTag type;
@@ -2102,6 +2106,7 @@ typedef struct Constraint
RangeVar *pktable; /* Primary key table */
List *fk_attrs; /* Attributes of foreign key */
List *pk_attrs; /* Corresponding attrs in PK table */
+ List *fk_reftypes; /* Per-column reference semantics (int List) */
char fk_matchtype; /* FULL, PARTIAL, SIMPLE */
char fk_upd_action; /* ON UPDATE action */
char fk_del_action; /* ON DELETE action */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..d3f4803006 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -140,6 +140,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
+PG_KEYWORD("element", ELEMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c730563f03..0a9eff4d04 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -737,6 +737,17 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -761,6 +772,19 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -942,6 +966,11 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -953,6 +982,15 @@ SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
101 | {} | {}
(1 row)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -971,6 +1009,14 @@ SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/element_foreign_key.out b/src/test/regress/expected/element_foreign_key.out
new file mode 100644
index 0000000000..b62f53e729
--- /dev/null
+++ b/src/test/regress/expected/element_foreign_key.out
@@ -0,0 +1,590 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fkarray"
+DETAIL: Key (ftest1)=({10,1}) is not present in table "pktableforarray".
+DROP TABLE FKTABLEFORARRAY;
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+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}', 17);
+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}', 18);
+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}', 19);
+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}}', 20);
+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, 21);
+ERROR: null value in column "ftest1" violates not-null constraint
+DETAIL: Failing row contains (null, 21).
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+ {1} | 4
+(11 rows)
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(A) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(B) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({4}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey"
+DETAIL: Key (ftest1)=({1,2,5}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+ERROR: foreign key constraint "fktableforarray_ftest1_fkey" cannot be implemented
+DETAIL: Key column "ftest1" has element type integer which does not have a default btree operator class that's compatible with class "float8_ops".
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Composite primary keys
+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');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(A, {A,B,C}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(B, {A}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2010,99)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2011,1)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[], FOREIGN KEY (EACH ELEMENT OF c) REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(2) is still referenced from table "fktableforarray".
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_c_fkey" on table "fktableforarray"
+DETAIL: Key (c)=(1) is still referenced from table "fktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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');
+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;
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(2, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(20, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+ERROR: foreign keys support only one array column
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: foreign keys support only one array column
+DROP TABLE F1;
+-- Cleanup
+DROP TABLE DIM1;
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "x2_x1_fkey"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "fk_const"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+DROP TABLE x1;
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{100,100,100},{NULL,NULL,20},{7,8,10}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fcf8bd7565..25ef369951 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1811,6 +1811,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1878,7 +1879,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index eefdeeacae..d88ff3772c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
+test: select_views portals_p2 foreign_key element_foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 76b0de30a7..9cb8638b4e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -140,6 +140,7 @@ test: amutils
test: select_views
test: portals_p2
test: foreign_key
+test: element_foreign_key
test: cluster
test: dependency
test: guc
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 25dd4e2c6d..f6fb507bdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -313,8 +313,10 @@ SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -325,11 +327,14 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/element_foreign_key.sql b/src/test/regress/sql/element_foreign_key.sql
new file mode 100644
index 0000000000..8c1e4d9601
--- /dev/null
+++ b/src/test/regress/sql/element_foreign_key.sql
@@ -0,0 +1,452 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 17);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 18);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 19);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 20);
+INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 21);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using INT4 keys coerced from INT2
+CREATE TABLE PKTABLEFORARRAY ( ptest1 int4 PRIMARY KEY, ptest2 text );
+
+-- Populate the primary table
+INSERT INTO PKTABLEFORARRAY VALUES (1, 'Test 1');
+INSERT INTO PKTABLEFORARRAY VALUES (2, 'Test 2');
+INSERT INTO PKTABLEFORARRAY VALUES (3, 'Test 3');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int2[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1], 1);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[2], 2);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[3], 3);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,3], 4);
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[4], 5);
+INSERT INTO FKTABLEFORARRAY VALUES (ARRAY[1,2,5], 6);
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Composite primary keys
+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');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+
+-- Check FK with cast
+CREATE TABLE PKTABLEFORARRAY (c smallint PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (c int[], FOREIGN KEY (EACH ELEMENT OF c) REFERENCES PKTABLEFORARRAY);
+INSERT INTO PKTABLEFORARRAY VALUES (1), (2);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,2}');
+UPDATE PKTABLEFORARRAY SET c = 3 WHERE c = 2;
+DELETE FROM PKTABLEFORARRAY WHERE c = 1;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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;
+
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+
+
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+
+
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+-- Cleanup
+DROP TABLE DIM1;
+
+
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+DROP TABLE x2;
+DROP TABLE x1;
+
+
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+DROP TABLE F1;
+
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+DROP TABLE F1;
+
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
On 2017-07-24 23:31, Mark Rofail wrote:
On Mon, Jul 24, 2017 at 11:25 PM, Erik Rijkers <er@xs4all.nl> wrote:
This patch doesn't apply to HEAD at the moment ( e2c8100e6072936 ).
My bad, I should have mentioned that the patch is dependant on the
original
patch.
Here is a *unified* patch that I just tested.
Thanks. Apply is now good, but I get this error when compiling:
ELEMENT' not present in UNRESERVED_KEYWORD section of gram.y
make[4]: *** [gram.c] Error 1
make[3]: *** [parser/gram.h] Error 2
make[2]: *** [../../src/include/parser/gram.h] Error 2
make[1]: *** [all-common-recurse] Error 2
make: *** [all-src-recurse] Error 2
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Jul 24, 2017 at 11:44 PM, Erik Rijkers <er@xs4all.nl> wrote:
Thanks. Apply is now good, but I get this error when compiling:
Well, this is embarrassing, okay, I cloned the repo and tested the fixed
patch and here it is.
Thanks for your patience.
Attachments:
Array-ELEMENT-foreign-key-v4 (fixed).patchtext/x-patch; charset=US-ASCII; name="Array-ELEMENT-foreign-key-v4 (fixed).patch"Download
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ea655a10a8..b4aefd7aa3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2324,7 +2324,17 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</row>
<row>
- <entry><structfield>conpfeqop</structfield></entry>
+ <entry><structfield>confreftype</structfield></entry>
+ <entry><type>char[]</type></entry>
+ <entry></entry>
+ <entry>If a foreign key, the reference semantics for each column:
+ <literal>p</> = plain (simple equality),
+ <literal>e</> = each element of referencing array must have a match
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>conpfeqop</structfield></entry>
<entry><type>oid[]</type></entry>
<entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
<entry>If a foreign key, list of the equality operators for PK = FK comparisons</entry>
@@ -2369,6 +2379,12 @@ SCRAM-SHA-256$<replaceable><iteration count></>:<replaceable><salt><
</table>
<para>
+ When <structfield>confreftype</structfield> indicates array-vs-scalar
+ foreign key reference semantics, the equality operators listed in
+ <structfield>conpfeqop</structfield> etc are for the array's element type.
+ </para>
+
+ <para>
In the case of an exclusion constraint, <structfield>conkey</structfield>
is only useful for constraint elements that are simple column references.
For other cases, a zero appears in <structfield>conkey</structfield>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index b05a9c2150..75da196334 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -882,6 +882,111 @@ CREATE TABLE order_items (
</para>
</sect2>
+ <sect2 id="ddl-constraints-element-fk">
+ <title>Array ELEMENT Foreign Keys</title>
+
+ <indexterm>
+ <primary>ELEMENT foreign key</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>Array ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>constraint</primary>
+ <secondary>ELEMENT foreign key</secondary>
+ </indexterm>
+
+ <indexterm>
+ <primary>referential integrity</primary>
+ </indexterm>
+
+ <para>
+ Another option you have with foreign keys is to use a
+ referencing column which is an array of elements with
+ the same type (or a compatible one) as the referenced
+ column in the related table. This feature is called
+ <firstterm>array element foreign key</firstterm> and is implemented
+ in PostgreSQL with <firstterm>ELEMENT foreign key constraints</firstterm>,
+ as described in the following example:
+
+<programlisting>
+CREATE TABLE drivers (
+ driver_id integer PRIMARY KEY,
+ first_name text,
+ last_name text,
+ ...
+);
+
+CREATE TABLE races (
+ race_id integer PRIMARY KEY,
+ title text,
+ race_day DATE,
+ ...
+ final_positions integer[] <emphasis>ELEMENT REFERENCES drivers</emphasis>
+);
+</programlisting>
+
+ The above example uses an array (<literal>final_positions</literal>)
+ to store the results of a race: for each of its elements
+ a referential integrity check is enforced on the
+ <literal>drivers</literal> table.
+ Note that <literal>ELEMENT REFERENCES</literal> is an extension
+ of PostgreSQL and it is not included in the SQL standard.
+ </para>
+
+ <para>
+ Even though the most common use case for array <literal>ELEMENT</literal>
+ foreign keys is on a single column key, you can define an <quote>array
+ <literal>ELEMENT</literal> foreign key constraint</quote> on a group
+ of columns. As the following example shows, it must be written in table
+ constraint form:
+
+<programlisting>
+CREATE TABLE available_moves (
+ kind text,
+ move text,
+ description text,
+ PRIMARY KEY (kind, move)
+);
+
+CREATE TABLE paths (
+ description text,
+ kind text,
+ moves text[],
+ <emphasis>FOREIGN KEY (kind, ELEMENT moves) REFERENCES available_moves (kind, move)</emphasis>
+);
+
+INSERT INTO available_moves VALUES ('relative', 'LN', 'look north');
+INSERT INTO available_moves VALUES ('relative', 'RL', 'rotate left');
+INSERT INTO available_moves VALUES ('relative', 'RR', 'rotate right');
+INSERT INTO available_moves VALUES ('relative', 'MF', 'move forward');
+INSERT INTO available_moves VALUES ('absolute', 'N', 'move north');
+INSERT INTO available_moves VALUES ('absolute', 'S', 'move south');
+INSERT INTO available_moves VALUES ('absolute', 'E', 'move east');
+INSERT INTO available_moves VALUES ('absolute', 'W', 'move west');
+
+INSERT INTO paths VALUES ('L-shaped path', 'relative', '{LN, RL, MF, RR, MF, MF}');
+INSERT INTO paths VALUES ('L-shaped path', 'absolute', '{W, N, N}');
+</programlisting>
+
+ On top of standard foreign key requirements,
+ array <literal>ELEMENT</literal> foreign key constraints
+ require that the referencing column is an array of a compatible
+ type of the corresponding referenced column.
+ </para>
+
+ <para>
+ For more detailed information on array <literal>ELEMENT</literal>
+ foreign key options and special cases, please refer to the documentation
+ for <xref linkend="sql-createtable-foreign-key"> and
+ <xref linkend="sql-createtable-element-foreign-key-constraints">.
+ </para>
+
+ </sect2>
+
<sect2 id="ddl-constraints-exclusion">
<title>Exclusion Constraints</title>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9c2c49533..0228fbe941 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -65,7 +65,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
UNIQUE <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY <replaceable class="PARAMETER">index_parameters</replaceable> |
- REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
+ [ELEMENT] REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -76,7 +76,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
UNIQUE ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
PRIMARY KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) <replaceable class="PARAMETER">index_parameters</replaceable> |
EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
- FOREIGN KEY ( <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
+ FOREIGN KEY ( [ELEMENT] <replaceable class="PARAMETER">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="PARAMETER">reftable</replaceable> [ ( <replaceable class="PARAMETER">refcolumn</replaceable> [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -812,10 +812,10 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</listitem>
</varlistentry>
- <varlistentry>
+ <varlistentry id="sql-createtable-foreign-key" xreflabel="FOREIGN KEY">
<term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
- <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
+ <term><literal>FOREIGN KEY ( [ELEMENT] <replaceable class="parameter">column_name</replaceable> [, ... ] )
REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
[ MATCH <replaceable class="parameter">matchtype</replaceable> ]
[ ON DELETE <replaceable class="parameter">action</replaceable> ]
@@ -839,6 +839,21 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</para>
<para>
+ In case the column name <replaceable class="parameter">column</replaceable>
+ is prepended with the <literal>ELEMENT</literal> keyword and <replaceable
+ class="parameter">column</replaceable> is an array of elements compatible
+ with the corresponding <replaceable class="parameter">refcolumn</replaceable>
+ in <replaceable class="parameter">reftable</replaceable>, an
+ array <literal>ELEMENT</literal> foreign key constraint is put in place
+ (see <xref linkend="sql-createtable-element-foreign-key-constraints">
+ for more information).
+ Multi-column keys with more than one <literal>ELEMENT</literal> column
+ are currently not allowed.
+
+ It is advisable to index the refrencing column using GIN index as it considerably enhances the performance.
+ </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
@@ -901,7 +916,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<para>
Delete any rows referencing the deleted row, or update the
values of the referencing column(s) to the new values of the
- referenced columns, respectively.
+ referenced columns, respectively. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -910,7 +926,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
<term><literal>SET NULL</literal></term>
<listitem>
<para>
- Set the referencing column(s) to null.
+ Set the referencing column(s) to null. Currently not supported
+ with array <literal>ELEMENT</literal> foreign keys.
</para>
</listitem>
</varlistentry>
@@ -922,6 +939,8 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
Set the referencing column(s) to their default values.
(There must be a row in the referenced table matching the default
values, if they are not null, or the operation will fail.)
+ Currently not supported with array <literal>ELEMENT</literal>
+ foreign keys.
</para>
</listitem>
</varlistentry>
@@ -938,6 +957,61 @@ FROM ( { <replaceable class="PARAMETER">numeric_literal</replaceable> | <replace
</varlistentry>
<varlistentry>
+ <varlistentry id="sql-createtable-element-foreign-key-constraints" xreflabel="ELEMENT REFERENCES">
+ <term><literal>ELEMENT REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">action</replaceable> ] [ ON UPDATE <replaceable class="parameter">action</replaceable> ]</literal> (column constraint)</term>
+
+ <listitem>
+ <para>
+ The <literal>ELEMENT REFERENCES</literal> definition specifies
+ an <quote>array <literal>ELEMENT</literal> foreign key</quote>,
+ a special kind of foreign key
+ constraint requiring the referencing column to be an array of elements
+ of the same type (or a compatible one) as the referenced column
+ in the referenced table. The value of each element of the
+ <replaceable class="parameter">refcolumn</replaceable> array
+ will be matched against some row of <replaceable
+ class="parameter">reftable</replaceable>.
+ </para>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys are an extension
+ of PostgreSQL and are not included in the SQL standard.
+ </para>
+
+ <para>
+ Even with <literal>ELEMENT</literal> foreign keys, modifications
+ in the referenced column can trigger actions to be performed on
+ the referencing array.
+ Similarly to standard foreign keys, you can specify these
+ actions using the <literal>ON DELETE</literal> and
+ <literal>ON UPDATE</literal> clauses.
+ However, only the two following actions for each clause are
+ currently allowed:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>NO ACTION</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints. This is the default action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Same as standard foreign key constraints.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<term><literal>DEFERRABLE</literal></term>
<term><literal>NOT DEFERRABLE</literal></term>
<listitem>
@@ -1876,6 +1950,16 @@ CREATE TABLE cities_ab_10000_to_100000
</refsect2>
<refsect2>
+ <title id="sql-createtable-foreign-key-arrays">Array <literal>ELEMENT</literal> Foreign Keys</title>
+
+ <para>
+ Array <literal>ELEMENT</literal> foreign keys and the
+ <literal>ELEMENT REFERENCES</literal> clause
+ are a <productname>PostgreSQL</productname> extension.
+ </para>
+ </refsect2>
+
+ <refsect2>
<title><literal>PARTITION BY</> Clause</title>
<para>
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index a5238c3af5..416ed60b0c 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -43,7 +44,7 @@ ginarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
&elmlen, &elmbyval, &elmalign);
deconstruct_array(array,
@@ -79,7 +80,8 @@ Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
/* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ ArrayType *array;
+ Datum elem;
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -94,45 +96,64 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* only items that match the queried element
+ are considered candidate */
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+
+ elem = PG_GETARG_DATUM(0);
+ PG_RETURN_POINTER(elem);
+ }
+ else
+ {
+ array = PG_GETARG_ARRAYTYPE_P_COPY(0);
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
- *nkeys = nelems;
- *nullFlags = nulls;
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
- switch (strategy)
- {
- case GinOverlapStrategy:
- *searchMode = GIN_SEARCH_MODE_DEFAULT;
- break;
- case GinContainsStrategy:
- if (nelems > 0)
+ *nkeys = nelems;
+ *nullFlags = nulls;
+
+ switch (strategy)
+ {
+ case GinOverlapStrategy:
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else /* everything contains the empty set */
- *searchMode = GIN_SEARCH_MODE_ALL;
- break;
- case GinContainedStrategy:
- /* empty set is contained in everything */
- *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- case GinEqualStrategy:
- if (nelems > 0)
+ break;
+ case GinContainsElemStrategy:
+ /* only items that match the queried element
+ are considered candidate */
*searchMode = GIN_SEARCH_MODE_DEFAULT;
- else
+ break;
+ case GinContainsStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else /* everything contains the empty set */
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ break;
+ case GinContainedStrategy:
+ /* empty set is contained in everything */
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
- break;
- default:
- elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
- strategy);
+ break;
+ case GinEqualStrategy:
+ if (nelems > 0)
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ else
+ *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
+ break;
+ default:
+ elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
+ strategy);
}
/* we should not free array, elems[i] points into it */
PG_RETURN_POINTER(elems);
+ }
}
/*
@@ -171,6 +192,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainsStrategy:
/* result is not lossy */
*recheck = false;
@@ -258,7 +280,8 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
- case GinContainsStrategy:
+ case GinContainsElemStrategy:
+ case GinContainsStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..3bd0b772a4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2098,6 +2098,7 @@ StoreRelCheck(Relation rel, char *ccname, Node *expr,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d25b39bb54..4e0d0bdda0 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1211,6 +1211,7 @@ index_constraint_create(Relation heapRelation,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 1336c46d3f..c197cec11a 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -59,6 +59,7 @@ CreateConstraintEntry(const char *constraintName,
Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const char *foreignRefType,
const Oid *pfEqOp,
const Oid *ppEqOp,
const Oid *ffEqOp,
@@ -82,6 +83,7 @@ CreateConstraintEntry(const char *constraintName,
Datum values[Natts_pg_constraint];
ArrayType *conkeyArray;
ArrayType *confkeyArray;
+ ArrayType *confreftypeArray;
ArrayType *conpfeqopArray;
ArrayType *conppeqopArray;
ArrayType *conffeqopArray;
@@ -119,7 +121,11 @@ CreateConstraintEntry(const char *constraintName,
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = Int16GetDatum(foreignKey[i]);
confkeyArray = construct_array(fkdatums, foreignNKeys,
- INT2OID, 2, true, 's');
+ INT2OID, sizeof(int16), true, 's');
+ for (i = 0; i < foreignNKeys; i++)
+ fkdatums[i] = CharGetDatum(foreignRefType[i]);
+ confreftypeArray = construct_array(fkdatums, foreignNKeys,
+ CHAROID, sizeof(char), true, 'c');
for (i = 0; i < foreignNKeys; i++)
fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]);
conpfeqopArray = construct_array(fkdatums, foreignNKeys,
@@ -136,6 +142,7 @@ CreateConstraintEntry(const char *constraintName,
else
{
confkeyArray = NULL;
+ confreftypeArray = NULL;
conpfeqopArray = NULL;
conppeqopArray = NULL;
conffeqopArray = NULL;
@@ -188,6 +195,11 @@ CreateConstraintEntry(const char *constraintName,
else
nulls[Anum_pg_constraint_confkey - 1] = true;
+ if (confreftypeArray)
+ values[Anum_pg_constraint_confreftype - 1] = PointerGetDatum(confreftypeArray);
+ else
+ nulls[Anum_pg_constraint_confreftype - 1] = true;
+
if (conpfeqopArray)
values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bb00858ad1..dc18fd1eae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -6995,6 +6995,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Relation pkrel;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
+ char fkreftypes[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
Oid fktypoid[INDEX_MAX_KEYS];
Oid opclasses[INDEX_MAX_KEYS];
@@ -7002,10 +7003,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
Oid ppeqoperators[INDEX_MAX_KEYS];
Oid ffeqoperators[INDEX_MAX_KEYS];
int i;
+ ListCell *lc;
int numfks,
numpks;
Oid indexOid;
Oid constrOid;
+ bool has_array;
bool old_check_ok;
ObjectAddress address;
ListCell *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
@@ -7082,6 +7085,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
*/
MemSet(pkattnum, 0, sizeof(pkattnum));
MemSet(fkattnum, 0, sizeof(fkattnum));
+ MemSet(fkreftypes, 0, sizeof(fkreftypes));
MemSet(pktypoid, 0, sizeof(pktypoid));
MemSet(fktypoid, 0, sizeof(fktypoid));
MemSet(opclasses, 0, sizeof(opclasses));
@@ -7094,6 +7098,50 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
fkattnum, fktypoid);
/*
+ * Validate the reference semantics codes, too, and convert list to array
+ * format to pass to CreateConstraintEntry.
+ */
+ Assert(list_length(fkconstraint->fk_reftypes) == numfks);
+ has_array = false;
+ i = 0;
+ foreach(lc, fkconstraint->fk_reftypes)
+ {
+ char reftype = lfirst_int(lc);
+
+ switch (reftype)
+ {
+ case FKCONSTR_REF_PLAIN:
+ /* OK, nothing to do */
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ /* At most one FK column can be an array reference */
+ if (has_array)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign keys support only one array column")));
+ has_array = true;
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d", (int) reftype);
+ break;
+ }
+ fkreftypes[i] = reftype;
+ i++;
+ }
+
+ /* Array foreign keys support only NO ACTION and RESTRICT actions */
+ if (has_array)
+ {
+ 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("array foreign keys support only NO ACTION and RESTRICT actions")));
+ }
+
+ /*
* If the attribute list for the referenced table was omitted, lookup the
* definition of the primary key and use it. Otherwise, validate the
* supplied attribute list. In either case, discover the index OID and
@@ -7179,6 +7227,65 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
eqstrategy = BTEqualStrategyNumber;
/*
+ * If this is an array foreign key, we must look up the operators for
+ * the array element type, not the array type itself.
+ */
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ Oid elemopclass;
+
+ /* We look through any domain here */
+ fktype = get_base_element_type(fktype);
+ if (!OidIsValid(fktype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FOREIGN_KEY),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has type %s which is not an array type.",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktypoid[i]))));
+
+ /*
+ * For the moment, we must also insist that the array's element
+ * type have a default btree opclass that is in the index's
+ * opfamily. This is necessary because ri_triggers.c relies on
+ * COUNT(DISTINCT x) on the element type, as well as on array_eq()
+ * on the array type, and we need those operations to have the
+ * same notion of equality that we're using otherwise.
+ *
+ * XXX this restriction is pretty annoying, considering the effort
+ * that's been put into the rest of the RI mechanisms to make them
+ * work with nondefault equality operators. In particular, it
+ * means that the cast-to-PK-datatype code path isn't useful for
+ * array-to-scalar references.
+ */
+ elemopclass = GetDefaultOpClass(fktype, BTREE_AM_OID);
+ if (!OidIsValid(elemopclass) ||
+ get_opclass_family(elemopclass) != opfamily)
+ {
+ /* Get the index opclass's name for the error message. */
+ char *opcname;
+
+ cla_ht = SearchSysCache1(CLAOID,
+ ObjectIdGetDatum(opclasses[i]));
+ if (!HeapTupleIsValid(cla_ht))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ opclasses[i]);
+ cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+ opcname = pstrdup(NameStr(cla_tup->opcname));
+ ReleaseSysCache(cla_ht);
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key column \"%s\" has element type %s which does not have a default btree operator class that's compatible with class \"%s\".",
+ strVal(list_nth(fkconstraint->fk_attrs, i)),
+ format_type_be(fktype),
+ opcname)));
+ }
+ }
+
+ /*
* There had better be a primary equality operator for the index.
* We'll use it for PK = PK comparisons.
*/
@@ -7239,14 +7346,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("foreign key constraint \"%s\" "
- "cannot be implemented",
- fkconstraint->conname),
- errdetail("Key columns \"%s\" and \"%s\" "
- "are of incompatible types: %s and %s.",
+ errmsg("foreign key constraint \"%s\" cannot be implemented",
+ fkconstraint->conname),
+ errdetail("Key columns \"%s\" and \"%s\" are of incompatible types: %s and %s.",
strVal(list_nth(fkconstraint->fk_attrs, i)),
strVal(list_nth(fkconstraint->pk_attrs, i)),
- format_type_be(fktype),
+ format_type_be(fktypoid[i]),
format_type_be(pktype))));
if (old_check_ok)
@@ -7275,6 +7380,13 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* We may assume that pg_constraint.conkey is not changing.
*/
old_fktype = tab->oldDesc->attrs[fkattnum[i] - 1]->atttypid;
+ if (fkreftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ old_fktype = get_base_element_type(old_fktype);
+ /* this shouldn't happen ... */
+ if (!OidIsValid(old_fktype))
+ elog(ERROR, "old foreign key column is not an array");
+ }
new_fktype = fktype;
old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
&old_castfunc);
@@ -7317,7 +7429,6 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
new_castfunc == old_castfunc &&
(!IsPolymorphicType(pfeqop_right) ||
new_fktype == old_fktype));
-
}
pfeqoperators[i] = pfeqop;
@@ -7341,6 +7452,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
indexOid,
RelationGetRelid(pkrel),
pkattnum,
+ fkreftypes,
pfeqoperators,
ppeqoperators,
ffeqoperators,
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b502941b08..cb017cbe14 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -635,6 +635,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
@@ -1006,6 +1007,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
char fk_matchtype = FKCONSTR_MATCH_SIMPLE;
List *fk_attrs = NIL;
List *pk_attrs = NIL;
+ List *fk_reftypes = NIL;
StringInfoData buf;
int funcnum;
OldTriggerInfo *info = NULL;
@@ -1035,7 +1037,10 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
if (i % 2)
fk_attrs = lappend(fk_attrs, arg);
else
- pk_attrs = lappend(pk_attrs, arg);
+ {
+ pk_attrs = lappend(pk_attrs, arg);
+ fk_reftypes = lappend_int(fk_reftypes, FKCONSTR_REF_PLAIN);
+ }
}
/* Prepare description of constraint for use in messages */
@@ -1174,6 +1179,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
fkcon->conname = constr_name;
fkcon->fk_attrs = fk_attrs;
fkcon->pk_attrs = pk_attrs;
+ fkcon->fk_reftypes = fk_reftypes;
fkcon->fk_matchtype = fk_matchtype;
switch (info->funcoids[0])
{
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index c2fc59d1aa..4f089d53a9 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3075,6 +3075,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
NULL,
NULL,
NULL,
+ NULL,
0,
' ',
' ',
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 45a04b0b27..01238bfd71 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2843,6 +2843,7 @@ _copyConstraint(const Constraint *from)
COPY_NODE_FIELD(pktable);
COPY_NODE_FIELD(fk_attrs);
COPY_NODE_FIELD(pk_attrs);
+ COPY_NODE_FIELD(fk_reftypes);
COPY_SCALAR_FIELD(fk_matchtype);
COPY_SCALAR_FIELD(fk_upd_action);
COPY_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8d92c03633..d35f3d1d9a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2575,6 +2575,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
COMPARE_NODE_FIELD(pktable);
COMPARE_NODE_FIELD(fk_attrs);
COMPARE_NODE_FIELD(pk_attrs);
+ COMPARE_NODE_FIELD(fk_reftypes);
COMPARE_SCALAR_FIELD(fk_matchtype);
COMPARE_SCALAR_FIELD(fk_upd_action);
COMPARE_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 379d92a2b0..119d100b37 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3490,6 +3490,7 @@ _outConstraint(StringInfo str, const Constraint *node)
WRITE_NODE_FIELD(pktable);
WRITE_NODE_FIELD(fk_attrs);
WRITE_NODE_FIELD(pk_attrs);
+ WRITE_NODE_FIELD(fk_reftypes);
WRITE_CHAR_FIELD(fk_matchtype);
WRITE_CHAR_FIELD(fk_upd_action);
WRITE_CHAR_FIELD(fk_del_action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4b1ce09c44..d1df75dd6e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -126,6 +126,13 @@ typedef struct ImportQual
List *table_names;
} ImportQual;
+/* Private struct for the result of foreign_key_column_elem production */
+typedef struct FKColElem
+{
+ Node *name; /* name of the column (a String) */
+ char reftype; /* FKCONSTR_REF_xxx code */
+} FKColElem;
+
/* ConstraintAttributeSpec yields an integer bitmask of these flags: */
#define CAS_NOT_DEFERRABLE 0x01
#define CAS_DEFERRABLE 0x02
@@ -183,6 +190,7 @@ static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
static void SplitColQualList(List *qualList,
List **constraintList, CollateClause **collClause,
core_yyscan_t yyscanner);
+static void SplitFKColElems(List *fkcolelems, List **names, List **reftypes);
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
@@ -233,6 +241,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
A_Indices *aind;
ResTarget *target;
struct PrivTarget *privtarget;
+ struct FKColElem *fkcolelem;
AccessPriv *accesspriv;
struct ImportQual *importqual;
InsertStmt *istmt;
@@ -358,6 +367,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <accesspriv> privilege
%type <list> privileges privilege_list
%type <privtarget> privilege_target
+%type <fkcolelem> foreign_key_column_elem
%type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
%type <list> function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
%type <ival> defacl_privilege_target
@@ -392,7 +402,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
execute_param_clause using_clause returning_clause
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
- relation_expr_list dostmt_opt_list
+ relation_expr_list dostmt_opt_list foreign_key_column_list
transform_element_list transform_type_list
TriggerTransitions TriggerReferencing
publication_name_list
@@ -622,8 +632,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
DOUBLE_P DROP
- EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
- EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
+ EACH ELEMENT ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE
+ EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
@@ -3437,14 +3447,16 @@ ColConstraintElem:
| REFERENCES qualified_name opt_column_list key_match key_actions
{
Constraint *n = makeNode(Constraint);
- n->contype = CONSTR_FOREIGN;
- n->location = @1;
- n->pktable = $2;
- n->fk_attrs = NIL;
- n->pk_attrs = $3;
- n->fk_matchtype = $4;
- n->fk_upd_action = (char) ($5 >> 8);
- n->fk_del_action = (char) ($5 & 0xFF);
+ n->contype = CONSTR_FOREIGN;
+ n->location = @1;
+ n->pktable = $2;
+ /* fk_attrs will be filled in by parse analysis */
+ n->fk_attrs = NIL;
+ n->pk_attrs = $3;
+ n->fk_reftypes = list_make1_int(FKCONSTR_REF_PLAIN);
+ n->fk_matchtype = $4;
+ n->fk_upd_action = (char) ($5 >> 8);
+ n->fk_del_action = (char) ($5 & 0xFF);
n->skip_validation = false;
n->initially_valid = true;
$$ = (Node *)n;
@@ -3635,14 +3647,15 @@ ConstraintElem:
NULL, yyscanner);
$$ = (Node *)n;
}
- | FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
- opt_column_list key_match key_actions ConstraintAttributeSpec
+ | FOREIGN KEY '(' foreign_key_column_list ')' REFERENCES
+ qualified_name opt_column_list key_match key_actions
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_FOREIGN;
n->location = @1;
+ SplitFKColElems($4, &n->fk_attrs, &n->fk_reftypes);
n->pktable = $7;
- n->fk_attrs = $4;
n->pk_attrs = $8;
n->fk_matchtype = $9;
n->fk_upd_action = (char) ($10 >> 8);
@@ -3675,7 +3688,30 @@ columnElem: ColId
$$ = (Node *) makeString($1);
}
;
-
+ foreign_key_column_list:
+ foreign_key_column_elem
+ { $$ = list_make1($1); }
+ | foreign_key_column_list ',' foreign_key_column_elem
+ { $$ = lappend($1, $3); }
+ ;
+
+ foreign_key_column_elem:
+ ColId
+ {
+ FKColElem *n = (FKColElem *) palloc(sizeof(FKColElem));
+ n->name = (Node *) makeString($1);
+ n->reftype = FKCONSTR_REF_PLAIN;
+ $$ = n;
+ }
+ | EACH ELEMENT OF ColId
+ {
+ FKColElem *n = (FKColElem *) palloc(sizeof(FKColElem));
+ n->name = (Node *) makeString($4);
+ n->reftype = FKCONSTR_REF_EACH_ELEMENT;
+ $$ = n;
+ }
+ ;
+
key_match: MATCH FULL
{
$$ = FKCONSTR_MATCH_FULL;
@@ -14686,6 +14722,7 @@ unreserved_keyword:
| DOUBLE_P
| DROP
| EACH
+ | ELEMENT
| ENABLE_P
| ENCODING
| ENCRYPTED
@@ -15803,6 +15840,23 @@ SplitColQualList(List *qualList,
*constraintList = qualList;
}
+/* Split a list of FKColElem structs into separate name and reftype lists */
+static void
+SplitFKColElems(List *fkcolelems, List **names, List **reftypes)
+{
+ ListCell *lc;
+
+ *names = NIL;
+ *reftypes = NIL;
+ foreach(lc, fkcolelems)
+ {
+ FKColElem *fkcolelem = (FKColElem *) lfirst(lc);
+
+ *names = lappend(*names, fkcolelem->name);
+ *reftypes = lappend_int(*reftypes, fkcolelem->reftype);
+ }
+}
+
/*
* Process result of ConstraintAttributeSpec, and set appropriate bool flags
* in the output command node. Pass NULL for any flags the particular
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9f37f1b920..163719c7a8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -743,6 +743,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
* list of FK constraints to be processed later.
*/
constraint->fk_attrs = list_make1(makeString(column->colname));
+ /* grammar should have set fk_reftypes */
+ Assert(list_length(constraint->fk_reftypes) == 1);
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 34dadd6e19..8c9eb0c676 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4232,6 +4232,117 @@ arraycontained(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(result);
}
+/*
+ * array_contains_elem : checks an array for a spefific element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid element_type,
+ bool element_isnull, Oid collation, void **fn_extra)
+{
+ Oid arr_type = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it1;
+ FunctionCallInfoData locfcinfo;
+
+ if (arr_type != element_type)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare different element types")));
+
+ if (element_isnull)
+ return false;
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *)*fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arr_type)
+ {
+ typentry = lookup_type_cache(arr_type,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arr_type))));
+ *fn_extra = (void *)typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator to each pair of array elements.
+ */
+ InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it1, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt1;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt1 = array_iter_next(&it1, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. XXX this diverges from the "NULL=NULL" behavior of
+ * array_eq, should we act like that?
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair
+ */
+ locfcinfo.arg[0] = elt1;
+ locfcinfo.arg[1] = elem;
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+ if (oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid element_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool element_isnull = PG_ARGISNULL(1);
+ bool result;
+
+ result = array_contains_elem(array, elem, element_type,
+ element_isnull, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input. */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c2891e6fa1..9e7d66df7e 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -118,9 +118,11 @@ typedef struct RI_ConstraintInfo
char confupdtype; /* foreign key's ON UPDATE action */
char confdeltype; /* foreign key's ON DELETE action */
char confmatchtype; /* foreign key's match type */
+ bool has_array; /* true if any reftype is EACH_ELEMENT */
int nkeys; /* number of key columns */
int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
+ char fk_reftypes[RI_MAX_NUMKEYS]; /* reference semantics */
Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
@@ -204,7 +206,8 @@ static void ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype);
+ const char *rightop, Oid rightoptype,
+ char fkreftype);
static void ri_add_cast_to(StringInfo buf, Oid typid);
static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
static int ri_NullCheck(HeapTuple tup,
@@ -395,6 +398,7 @@ RI_FKey_check(TriggerData *trigdata)
if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
{
StringInfoData querybuf;
+ StringInfoData countbuf;
char pkrelname[MAX_QUOTED_REL_NAME_LEN];
char attname[MAX_QUOTED_NAME_LEN];
char paramname[16];
@@ -407,12 +411,22 @@ RI_FKey_check(TriggerData *trigdata)
* FOR KEY SHARE OF x
* The type id's for the $ parameters are those of the
* corresponding FK attributes.
+ *
+ * In case of an array ELEMENT foreign key, the previous query is used
+ * to count the number of matching rows and see if every combination
+ * is actually referenced.
+ * The wrapping query is
+ * SELECT 1 WHERE
+ * (SELECT count(DISTINCT y) FROM unnest($1) y)
+ * = (SELECT count(*) FROM (<QUERY>) z)
* ----------
*/
initStringInfo(&querybuf);
quoteRelationName(pkrelname, pk_rel);
appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
querysep = "WHERE";
+ initStringInfo(&countbuf);
+ appendStringInfo(&countbuf, "SELECT 1 WHERE ");
for (i = 0; i < riinfo->nkeys; i++)
{
Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
@@ -421,18 +435,41 @@ RI_FKey_check(TriggerData *trigdata)
quoteOneName(attname,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
sprintf(paramname, "$%d", i + 1);
+
+ /*
+ * In case of an array ELEMENT foreign key, we check that each
+ * distinct non-null value in the array is present in the PK
+ * table.
+ */
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&countbuf,
+ "(SELECT pg_catalog.count(DISTINCT y) FROM pg_catalog.unnest(%s) y)",
+ paramname);
+
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pf_eq_oprs[i],
- paramname, fk_type);
+ paramname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = fk_type;
}
appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
- /* Prepare and save the plan */
- qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
- &qkey, fk_rel, pk_rel, true);
+ if (riinfo->has_array)
+ {
+ appendStringInfo(&countbuf,
+ " OPERATOR(pg_catalog.=) (SELECT pg_catalog.count(*) FROM (%s) z)",
+ querybuf.data);
+
+ /* Prepare and save the plan for array ELEMENT foreign keys */
+ qplan = ri_PlanCheck(countbuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
+ }
+ else
+ /* Prepare and save the plan */
+ qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+ &qkey, fk_rel, pk_rel, true);
}
/*
@@ -559,7 +596,8 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
ri_GenerateQual(&querybuf, querysep,
attname, pk_type,
riinfo->pp_eq_oprs[i],
- paramname, pk_type);
+ paramname, pk_type,
+ FKCONSTR_REF_PLAIN);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -751,7 +789,8 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -974,7 +1013,8 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1130,7 +1170,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&querybuf, querysep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = "AND";
queryoids[i] = pk_type;
}
@@ -1309,7 +1350,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1475,7 +1517,8 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1651,7 +1694,8 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -1817,7 +1861,8 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2008,7 +2053,8 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
ri_GenerateQual(&qualbuf, qualsep,
paramname, pk_type,
riinfo->pf_eq_oprs[i],
- attname, fk_type);
+ attname, fk_type,
+ riinfo->fk_reftypes[i]);
querysep = ",";
qualsep = "AND";
queryoids[i] = pk_type;
@@ -2327,6 +2373,14 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* For MATCH FULL:
* (fk.keycol1 IS NOT NULL [OR ...])
*
+ * In case of an array ELEMENT column, relname is replaced with the
+ * following subquery:
+ *
+ * SELECT unnest("keycol1") k1, "keycol1" ak1 [, ...]
+ * FROM ONLY "public"."fk"
+ *
+ * where all the columns are renamed in order to prevent name collisions.
+ *
* We attach COLLATE clauses to the operators when comparing columns
* that have different collations.
*----------
@@ -2338,15 +2392,46 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
{
quoteOneName(fkattname,
RIAttName(fk_rel, riinfo->fk_attnums[i]));
- appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+ if (riinfo->has_array)
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%sfk.ak%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.k%d %s", sep, i + 1,
+ fkattname);
+ else
+ appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
sep = ", ";
}
quoteRelationName(pkrelname, pk_rel);
quoteRelationName(fkrelname, fk_rel);
- appendStringInfo(&querybuf,
- " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
- fkrelname, pkrelname);
+
+ if (riinfo->has_array)
+ {
+ sep = "";
+ appendStringInfo(&querybuf,
+ " FROM (SELECT ");
+ for (i = 0; i < riinfo->nkeys; i++)
+ {
+ quoteOneName(fkattname,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->fk_reftypes[i] == FKCONSTR_REF_EACH_ELEMENT)
+ appendStringInfo(&querybuf, "%spg_catalog.unnest(%s) k%d, %s ak%d",
+ sep, fkattname, i + 1, fkattname, i + 1);
+ else
+ appendStringInfo(&querybuf, "%s%s k%d", sep, fkattname,
+ i + 1);
+ sep = ", ";
+ }
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s) fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
+ }
+ else
+ appendStringInfo(&querybuf,
+ " FROM ONLY %s fk LEFT OUTER JOIN ONLY %s pk ON",
+ fkrelname, pkrelname);
strcpy(pkattname, "pk.");
strcpy(fkattname, "fk.");
@@ -2360,12 +2445,16 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
quoteOneName(pkattname + 3,
RIAttName(pk_rel, riinfo->pk_attnums[i]));
- quoteOneName(fkattname + 3,
- RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname + 3, "k%d", i + 1);
+ else
+ quoteOneName(fkattname + 3,
+ RIAttName(fk_rel, riinfo->fk_attnums[i]));
ri_GenerateQual(&querybuf, sep,
pkattname, pk_type,
riinfo->pf_eq_oprs[i],
- fkattname, fk_type);
+ fkattname, fk_type,
+ FKCONSTR_REF_PLAIN);
if (pk_coll != fk_coll)
ri_GenerateQualCollation(&querybuf, pk_coll);
sep = "AND";
@@ -2381,7 +2470,10 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
sep = "";
for (i = 0; i < riinfo->nkeys; i++)
{
- quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
+ if (riinfo->has_array)
+ sprintf(fkattname, "k%d", i + 1);
+ else
+ quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
appendStringInfo(&querybuf,
"%sfk.%s IS NOT NULL",
sep, fkattname);
@@ -2557,25 +2649,29 @@ quoteRelationName(char *buffer, Relation rel)
/*
* ri_GenerateQual --- generate a WHERE clause equating two variables
*
- * The idea is to append " sep leftop op rightop" to buf. The complexity
- * comes from needing to be sure that the parser will select the desired
- * operator. We always name the operator using OPERATOR(schema.op) syntax
- * (readability isn't a big priority here), so as to avoid search-path
- * uncertainties. We have to emit casts too, if either input isn't already
- * the input type of the operator; else we are at the mercy of the parser's
- * heuristics for ambiguous-operator resolution.
- */
+ * The idea is to append " sep leftop op rightop" to buf, or if fkreftype is
+ * FKCONSTR_REF_EACH_ELEMENT, append " sep leftop @>> rightop" to buf.
+ *
+ * The complexity comes from needing to be sure that the parser will select
+ * the desired operator. We always name the operator using
+ * OPERATOR(schema.op) syntax (readability isn't a big priority here), so as
+ * to avoid search-path uncertainties. We have to emit casts too, if either
+ * input isn't already the input type of the operator; else we are at the
+ * mercy of the parser's heuristics for ambiguous-operator resolution.
+ */
static void
ri_GenerateQual(StringInfo buf,
const char *sep,
const char *leftop, Oid leftoptype,
Oid opoid,
- const char *rightop, Oid rightoptype)
+ const char *rightop, Oid rightoptype,
+ char fkreftype)
{
HeapTuple opertup;
Form_pg_operator operform;
char *oprname;
char *nspname;
+ Oid oprright;
opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(opoid));
if (!HeapTupleIsValid(opertup))
@@ -2586,15 +2682,46 @@ ri_GenerateQual(StringInfo buf,
nspname = get_namespace_name(operform->oprnamespace);
- appendStringInfo(buf, " %s %s", sep, leftop);
- if (leftoptype != operform->oprleft)
- 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);
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT)
+ {
+ oprright = get_array_type(operform->oprright);
+ if (!OidIsValid(oprright))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find array type for data type %s",
+ format_type_be(operform->oprright))));
+ }
+ else
+ oprright = operform->oprright;
+
+ if (fkreftype == FKCONSTR_REF_EACH_ELEMENT){
+ appendStringInfo(buf, " %s %s", sep, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ appendStringInfo(buf, " @>> ");
+
+ appendStringInfoString(buf, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+ }
+ else{
+ appendStringInfo(buf, " %s %s", sep, leftop);
+
+ if (leftoptype != operform->oprleft)
+ ri_add_cast_to(buf, operform->oprleft);
+
+ appendStringInfo(buf, " OPERATOR(%s.%s) ",
+ quote_identifier(nspname), oprname);
+
+ appendStringInfoString(buf, rightop);
+
+ if (rightoptype != oprright)
+ ri_add_cast_to(buf, oprright);
+ }
+
ReleaseSysCache(opertup);
}
@@ -2801,6 +2928,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
bool isNull;
ArrayType *arr;
int numkeys;
+ int i;
/*
* On the first call initialize the hashtable
@@ -2879,6 +3007,20 @@ ri_LoadConstraintInfo(Oid constraintOid)
pfree(arr); /* free de-toasted copy, if any */
adatum = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype, &isNull);
+ if (isNull)
+ elog(ERROR, "null confreftype for constraint %u", constraintOid);
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numkeys ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "confreftype is not a 1-D char array");
+ memcpy(riinfo->fk_reftypes, ARR_DATA_PTR(arr), numkeys * sizeof(char));
+ if ((Pointer) arr != DatumGetPointer(adatum))
+ pfree(arr); /* free de-toasted copy, if any */
+
+ adatum = SysCacheGetAttr(CONSTROID, tup,
Anum_pg_constraint_conpfeqop, &isNull);
if (isNull)
elog(ERROR, "null conpfeqop for constraint %u", constraintOid);
@@ -2921,6 +3063,24 @@ ri_LoadConstraintInfo(Oid constraintOid)
if ((Pointer) arr != DatumGetPointer(adatum))
pfree(arr); /* free de-toasted copy, if any */
+ /*
+ * Fix up some stuff for array foreign keys. We need a has_array flag
+ * indicating whether there's an array foreign key, and we want to set
+ * ff_eq_oprs[i] to array_eq() for array columns, because that's what
+ * makes sense for ri_KeysEqual, and we have no other use for ff_eq_oprs
+ * in this module. (If we did, substituting the array comparator at the
+ * call point in ri_KeysEqual might be more appropriate.)
+ */
+ riinfo->has_array = false;
+ for (i = 0; i < numkeys; i++)
+ {
+ if (riinfo->fk_reftypes[i] != FKCONSTR_REF_PLAIN)
+ {
+ riinfo->has_array = true;
+ riinfo->ff_eq_oprs[i] = ARRAY_EQ_OP;
+ }
+ }
+
ReleaseSysCache(tup);
/*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d83377d1d8..680623534c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -314,6 +314,9 @@ static char *pg_get_viewdef_worker(Oid viewoid,
static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
+static void decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
@@ -1882,7 +1885,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
{
case CONSTRAINT_FOREIGN:
{
- Datum val;
+ Datum colindexes;
+ Datum reftypes;
bool isnull;
const char *string;
@@ -1890,13 +1894,21 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
appendStringInfoString(&buf, "FOREIGN KEY (");
/* Fetch and build referencing-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_conkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conkey,
+ &isnull);
if (isnull)
elog(ERROR, "null conkey for constraint %u",
constraintId);
+ reftypes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confreftype,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null confreftype for constraint %u",
+ constraintId);
- decompile_column_index_array(val, conForm->conrelid, &buf);
+ decompile_fk_column_index_array(colindexes, reftypes,
+ conForm->conrelid, &buf);
/* add foreign relation name */
appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1904,13 +1916,15 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
NIL));
/* Fetch and build referenced-column list */
- val = SysCacheGetAttr(CONSTROID, tup,
- Anum_pg_constraint_confkey, &isnull);
+ colindexes = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_confkey,
+ &isnull);
if (isnull)
elog(ERROR, "null confkey for constraint %u",
constraintId);
- decompile_column_index_array(val, conForm->confrelid, &buf);
+ decompile_column_index_array(colindexes,
+ conForm->confrelid, &buf);
appendStringInfoChar(&buf, ')');
@@ -2185,6 +2199,66 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
}
}
+ /*
+ * Convert an int16[] Datum and a char[] Datum into a comma-separated
+ * list of column names for the indicated relation, prefixed by appropriate
+ * keywords depending on the foreign key reference semantics indicated by
+ * the char[] entries. Append the text to buf.
+ */
+ static void
+ decompile_fk_column_index_array(Datum column_index_array,
+ Datum fk_reftype_array,
+ Oid relId, StringInfo buf)
+ {
+ Datum *keys;
+ int nKeys;
+ Datum *reftypes;
+ int nReftypes;
+ int j;
+
+ /* Extract data from array of int16 */
+ deconstruct_array(DatumGetArrayTypeP(column_index_array),
+ INT2OID, sizeof(int16), true, 's',
+ &keys, NULL, &nKeys);
+
+ /* Extract data from array of char */
+ deconstruct_array(DatumGetArrayTypeP(fk_reftype_array),
+ CHAROID, sizeof(char), true, 'c',
+ &reftypes, NULL, &nReftypes);
+
+ if (nKeys != nReftypes)
+ elog(ERROR, "wrong confreftype cardinality");
+
+ for (j = 0; j < nKeys; j++)
+ {
+ char *colName;
+ const char *prefix;
+
+ colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+
+ switch (DatumGetChar(reftypes[j]))
+ {
+ case FKCONSTR_REF_PLAIN:
+ prefix = "";
+ break;
+ case FKCONSTR_REF_EACH_ELEMENT:
+ prefix = "EACH ELEMENT OF ";
+ break;
+ default:
+ elog(ERROR, "invalid fk_reftype: %d",
+ (int) DatumGetChar(reftypes[j]));
+ prefix = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ if (j == 0)
+ appendStringInfo(buf, "%s%s", prefix,
+ quote_identifier(colName));
+ else
+ appendStringInfo(buf, ", %s%s", prefix,
+ quote_identifier(colName));
+ }
+}
/* ----------
* get_expr - Decompile an expression tree
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index f850be490a..55a43d0b90 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -689,7 +689,8 @@ DATA(insert ( 2745 2277 2277 1 s 2750 2742 0 ));
DATA(insert ( 2745 2277 2277 2 s 2751 2742 0 ));
DATA(insert ( 2745 2277 2277 3 s 2752 2742 0 ));
DATA(insert ( 2745 2277 2277 4 s 1070 2742 0 ));
-
+DATA(insert ( 2745 2277 2283 5 s 6108 2742 0 )); /* anyarray @>> anyelem */
+
/*
* btree enum_ops
*/
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index ec035d8434..bea6f82404 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -104,8 +104,16 @@ CATALOG(pg_constraint,2606)
int16 confkey[1];
/*
+ * If a foreign key, the reference semantics for each column
+ */
+ char confreftype[1];
+
+ /*
* If a foreign key, the OIDs of the PK = FK equality operators for each
* column of the constraint
+ *
+ * Note: for array foreign keys, all these operators are for the array's
+ * element type.
*/
Oid conpfeqop[1];
@@ -150,7 +158,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
* compiler constants for pg_constraint
* ----------------
*/
-#define Natts_pg_constraint 24
+#define Natts_pg_constraint 25
#define Anum_pg_constraint_conname 1
#define Anum_pg_constraint_connamespace 2
#define Anum_pg_constraint_contype 3
@@ -169,12 +177,13 @@ typedef FormData_pg_constraint *Form_pg_constraint;
#define Anum_pg_constraint_connoinherit 16
#define Anum_pg_constraint_conkey 17
#define Anum_pg_constraint_confkey 18
-#define Anum_pg_constraint_conpfeqop 19
-#define Anum_pg_constraint_conppeqop 20
-#define Anum_pg_constraint_conffeqop 21
-#define Anum_pg_constraint_conexclop 22
-#define Anum_pg_constraint_conbin 23
-#define Anum_pg_constraint_consrc 24
+#define Anum_pg_constraint_confreftype 19
+#define Anum_pg_constraint_conpfeqop 20
+#define Anum_pg_constraint_conppeqop 21
+#define Anum_pg_constraint_conffeqop 22
+#define Anum_pg_constraint_conexclop 23
+#define Anum_pg_constraint_conbin 24
+#define Anum_pg_constraint_consrc 25
/* ----------------
* initial contents of pg_constraint
@@ -195,7 +204,9 @@ typedef FormData_pg_constraint *Form_pg_constraint;
/*
* Valid values for confupdtype and confdeltype are the FKCONSTR_ACTION_xxx
* constants defined in parsenodes.h. Valid values for confmatchtype are
- * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h.
+ * the FKCONSTR_MATCH_xxx constants defined in parsenodes.h. Valid values
+ * for elements of confreftype[] are the FKCONSTR_REF_xxx constants defined
+ * in parsenodes.h.
*/
#endif /* PG_CONSTRAINT_H */
diff --git a/src/include/catalog/pg_constraint_fn.h b/src/include/catalog/pg_constraint_fn.h
index a4c46897ed..06f4313bae 100644
--- a/src/include/catalog/pg_constraint_fn.h
+++ b/src/include/catalog/pg_constraint_fn.h
@@ -40,6 +40,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
Oid indexRelId,
Oid foreignRelId,
const int16 *foreignKey,
+ const char *foreignRefType,
const Oid *pfEqOp,
const Oid *ppEqOp,
const Oid *ffEqOp,
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ffabc2003b..8a9d616d29 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1570,6 +1570,8 @@ DESCR("contains");
DATA(insert OID = 2752 ( "<@" PGNSP PGUID b f f 2277 2277 16 2751 0 arraycontained arraycontsel arraycontjoinsel ));
DESCR("is contained by");
#define OID_ARRAY_CONTAINED_OP 2752
+DATA(insert OID = 6108 ( "@>>" PGNSP PGUID b f f 2277 2283 16 0 0 arraycontainselem arraycontsel arraycontjoinsel ));
+DESCR("containselem");
/* 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 8b33b4e0ea..01b207d363 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -4308,6 +4308,7 @@ DESCR("GIN array support (obsolete)");
DATA(insert OID = 2747 ( arrayoverlap PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arrayoverlap _null_ _null_ _null_ ));
DATA(insert OID = 2748 ( arraycontains PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontains _null_ _null_ _null_ ));
DATA(insert OID = 2749 ( arraycontained PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2277" _null_ _null_ _null_ _null_ _null_ arraycontained _null_ _null_ _null_ ));
+DATA(insert OID = 6109 ( arraycontainselem PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2277 2283" _null_ _null_ _null_ _null_ _null_ arraycontainselem _null_ _null_ _null_ ));
/* BRIN minmax */
DATA(insert OID = 3383 ( brin_minmax_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_opcinfo _null_ _null_ _null_ ));
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f2a4a75da..7662ef8018 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2067,6 +2067,10 @@ typedef enum ConstrType /* types of constraints */
#define FKCONSTR_MATCH_PARTIAL 'p'
#define FKCONSTR_MATCH_SIMPLE 's'
+ /* Foreign key column reference semantics codes */
+ #define FKCONSTR_REF_PLAIN 'p'
+ #define FKCONSTR_REF_EACH_ELEMENT 'e'
+
typedef struct Constraint
{
NodeTag type;
@@ -2102,6 +2106,7 @@ typedef struct Constraint
RangeVar *pktable; /* Primary key table */
List *fk_attrs; /* Attributes of foreign key */
List *pk_attrs; /* Corresponding attrs in PK table */
+ List *fk_reftypes; /* Per-column reference semantics (int List) */
char fk_matchtype; /* FULL, PARTIAL, SIMPLE */
char fk_upd_action; /* ON UPDATE action */
char fk_del_action; /* ON DELETE action */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f50e45e886..d3f4803006 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -140,6 +140,7 @@ PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD)
+PG_KEYWORD("element", ELEMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index c730563f03..0a9eff4d04 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -737,6 +737,17 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -761,6 +772,19 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -942,6 +966,11 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -953,6 +982,15 @@ SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
101 | {} | {}
(1 row)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -971,6 +1009,14 @@ SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/element_foreign_key.out b/src/test/regress/expected/element_foreign_key.out
new file mode 100644
index 0000000000..586eccab6e
--- /dev/null
+++ b/src/test/regress/expected/element_foreign_key.out
@@ -0,0 +1,553 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fkarray"
+DETAIL: Key (ftest1)=({10,1}) is not present in table "pktableforarray".
+DROP TABLE FKTABLEFORARRAY;
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+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}', 17);
+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}', 18);
+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}', 19);
+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}}', 20);
+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, 21);
+ERROR: null value in column "ftest1" violates not-null constraint
+DETAIL: Failing row contains (null, 21).
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+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} | 3
+ {2} | 4
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+(11 rows)
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+----------+--------
+ {1} | 3
+ {1} | 5
+ {3} | 6
+ {1} | 7
+ {4,5} | 8
+ {4,4} | 9
+ | 10
+ {} | 11
+ {1,NULL} | 12
+ {NULL} | 13
+ {1} | 4
+(11 rows)
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+ERROR: array foreign keys support only NO ACTION and RESTRICT actions
+DROP TABLE FKTABLEFORARRAY;
+ERROR: table "fktableforarray" does not exist
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(A) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_ftest1_fkey" on table "fktableforarray"
+DETAIL: Key (ptest1)=(B) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ ftest1 | ftest2
+---------+--------
+ {A} | 1
+ {B} | 2
+ {C} | 3
+ {A,B,C} | 4
+(4 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+ERROR: foreign key constraint "fktableforarray_ftest1_fkey" cannot be implemented
+DETAIL: Key column "ftest1" has element type integer which does not have a default btree operator class that's compatible with class "float8_ops".
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+-- Composite primary keys
+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');
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(A, {A,B,C}) is not present in table "pktableforarray".
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+ERROR: insert or update on table "fktableforarray" violates foreign key constraint "fktableforarray_fid1_fkey"
+DETAIL: Key (fid1, fid2)=(B, {A}) is not present in table "pktableforarray".
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+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".
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2010,99)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+ERROR: update or delete on table "pktableforarray" violates foreign key constraint "fktableforarray_invoice_ids_fkey" on table "fktableforarray"
+DETAIL: Key (id)=((2011,1)) is still referenced from table "fktableforarray".
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+ id | invoice_ids | ftest2
+----+-------------------------+-----------
+ 1 | {"(2010,99)"} | Product A
+ 2 | {"(2011,1)","(2011,2)"} | Product B
+ 3 | {"(2011,2)"} | Product C
+(3 rows)
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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');
+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;
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(2, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(20, {0,2,3,4,6}) is not present in table "dim1".
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_x_fkey"
+DETAIL: Key (x, y)=(4, {0,2,4}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+ERROR: foreign keys support only one array column
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+ERROR: foreign keys support only one array column
+DROP TABLE F1;
+-- Cleanup
+DROP TABLE DIM1;
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "x2_x1_fkey"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+ERROR: insert or update on table "x2" violates foreign key constraint "fk_const"
+DETAIL: Key (x1, x2)=({1,3}, 6) is not present in table "x1".
+DROP TABLE x2;
+DROP TABLE x1;
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{100,100,100},{NULL,NULL,20},{7,8,10}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+ERROR: insert or update on table "f1" violates foreign key constraint "f1_slots_fkey"
+DETAIL: Key (slots)=({{NULL,1,NULL},{NULL,NULL,11},{NULL,NULL,6}}) is not present in table "dim1".
+DROP TABLE F1;
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fcf8bd7565..25ef369951 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1811,6 +1811,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -1878,7 +1879,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(122 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index eefdeeacae..d88ff3772c 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: publication subscription
# ----------
# Another group of parallel tests
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
+test: select_views portals_p2 foreign_key element_foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass
# ----------
# Another group of parallel tests
# NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 76b0de30a7..9cb8638b4e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -140,6 +140,7 @@ test: amutils
test: select_views
test: portals_p2
test: foreign_key
+test: element_foreign_key
test: cluster
test: dependency
test: guc
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 25dd4e2c6d..f6fb507bdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -313,8 +313,10 @@ SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}";
SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -325,11 +327,14 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/element_foreign_key.sql b/src/test/regress/sql/element_foreign_key.sql
new file mode 100644
index 0000000000..8d5363efbf
--- /dev/null
+++ b/src/test/regress/sql/element_foreign_key.sql
@@ -0,0 +1,415 @@
+-- EACH-ELEMENT FK CONSTRAINTS
+
+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 (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 1);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check alter table with failing rows
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], ftest2 int );
+INSERT INTO FKTABLEFORARRAY VALUES ('{10,1}', 2);
+ALTER TABLE FKTABLEFORARRAY ADD CONSTRAINT FKARRAY FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAY;
+
+-- Check create table
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYMDIM ( ftest1 int[][], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+CREATE TABLE FKTABLEFORARRAYNOTNULL ( ftest1 int[] NOT NULL, FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Insert successful rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 3);
+INSERT INTO FKTABLEFORARRAY VALUES ('{2}', 4);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 5);
+INSERT INTO FKTABLEFORARRAY VALUES ('{3}', 6);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1}', 7);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,5}', 8);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,4}', 9);
+INSERT INTO FKTABLEFORARRAY VALUES (NULL, 10);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}', 11);
+INSERT INTO FKTABLEFORARRAY VALUES ('{1,NULL}', 12);
+INSERT INTO FKTABLEFORARRAY VALUES ('{NULL}', 13);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{1,2},{1,3}}', 14);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{4,5},{NULL,2},{NULL,3}}', 15);
+
+-- Insert failed rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('{6}', 16);
+INSERT INTO FKTABLEFORARRAY VALUES ('{4,6}', 17);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL}', 18);
+INSERT INTO FKTABLEFORARRAY VALUES ('{6,NULL,4,NULL}', 19);
+INSERT INTO FKTABLEFORARRAYMDIM VALUES ('{{1,2},{6,NULL}}', 20);
+INSERT INTO FKTABLEFORARRAYNOTNULL VALUES (NULL, 21);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE NO ACTION)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1=1;
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE NO ACTION)
+UPDATE PKTABLEFORARRAY SET ptest1=7 WHERE ptest1=1;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Check UPDATE on FKTABLE
+UPDATE FKTABLEFORARRAY SET ftest1=ARRAY[1] WHERE ftest2=4;
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE FKTABLEFORARRAYNOTNULL;
+DROP TABLE FKTABLEFORARRAYMDIM;
+
+-- Allowed references with actions (NO ACTION, RESTRICT)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+-- Not allowed references (SET NULL, SET DEFAULT, CASCADE)
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE NO ACTION, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE RESTRICT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET DEFAULT, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE SET NULL, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE NO ACTION ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE RESTRICT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE CASCADE ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET DEFAULT ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON DELETE SET NULL ON UPDATE CASCADE, ftest2 int );
+DROP TABLE FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Check reference on empty table
+CREATE TABLE PKTABLEFORARRAY (ptest1 int PRIMARY KEY);
+CREATE TABLE FKTABLEFORARRAY (ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY);
+INSERT INTO FKTABLEFORARRAY VALUES ('{}');
+DROP TABLE FKTABLEFORARRAY;
+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)[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY ON UPDATE RESTRICT ON DELETE RESTRICT, 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);
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE (must fail due to ON DELETE RESTRICT)
+DELETE FROM PKTABLEFORARRAY WHERE ptest1='A';
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE (must fail due to ON UPDATE RESTRICT)
+UPDATE PKTABLEFORARRAY SET ptest1='D' WHERE ptest1='B';
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Repeat a similar test using FLOAT8 keys coerced from INTEGER
+CREATE TABLE PKTABLEFORARRAY ( ptest1 float8 PRIMARY KEY, ptest2 text );
+-- XXX this really ought to work, but currently we must disallow it
+CREATE TABLE FKTABLEFORARRAY ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORARRAY, ftest2 int );
+
+-- Cleanup
+DROP TABLE PKTABLEFORARRAY;
+
+-- Composite primary keys
+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');
+
+-- Create the foreign table
+CREATE TABLE FKTABLEFORARRAY ( fid1 CHAR(1), fid2 CHAR(1)[], ftest2 text, FOREIGN KEY (fid1, EACH ELEMENT OF fid2) REFERENCES PKTABLEFORARRAY);
+
+-- Insert valid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B'], '1');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['C'], '2');
+
+-- Insert invalid rows into FK TABLE
+INSERT INTO FKTABLEFORARRAY VALUES ('A', ARRAY['A','B', 'C'], '3');
+INSERT INTO FKTABLEFORARRAY VALUES ('B', ARRAY['A'], '4');
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+
+-- Test ELEMENT foreign keys 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[], FOREIGN KEY (EACH ELEMENT OF invoice_ids) 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');
+
+-- Check FKTABLE
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Delete a row from PK TABLE
+DELETE FROM PKTABLEFORARRAY WHERE id=ROW(2010,99);
+
+-- Check FKTABLE for removal of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Update a row from PK TABLE
+UPDATE PKTABLEFORARRAY SET id=ROW(2011,99) WHERE id=ROW(2011,1);
+
+-- Check FKTABLE for update of matched row
+SELECT * FROM FKTABLEFORARRAY;
+
+-- Cleanup
+DROP TABLE FKTABLEFORARRAY;
+DROP TABLE PKTABLEFORARRAY;
+DROP TYPE INVOICEID;
+
+-- Check for an array column referencing another array column (NOT ELEMENT FOREIGN KEY)
+-- Create primary table with an array primary key
+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;
+
+-- ---------------------------------------
+-- Multi-column "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL, Y INTEGER NOT NULL, PRIMARY KEY (X, Y));
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT x.t, x.t * y.t
+ FROM (SELECT generate_series(1, 10) AS t) x,
+ (SELECT generate_series(0, 10) AS t) y;
+
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- FAILS (2 is not present)
+INSERT INTO F1 VALUES (4, '{0,NULL,4}'); -- OK
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Try updates
+UPDATE F1 SET y = '{0,2,4,6}' WHERE x = 2; -- OK
+UPDATE F1 SET y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS
+UPDATE F1 SET x = 20, y = '{0,2,3,4,6}' WHERE x = 2; -- FAILS (20 does not exist)
+UPDATE F1 SET y = '{0,4,8}' WHERE x = 4; -- OK
+UPDATE F1 SET y = '{0,5,NULL,10}' WHERE x = 5; -- OK
+DROP TABLE F1;
+
+
+-- Test with FOREIGN KEY after TABLE population
+CREATE TABLE F1 (
+ x INTEGER PRIMARY KEY, y INTEGER[]
+);
+-- Insert facts
+INSERT INTO F1 VALUES (1, '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES (2, '{0,2,4,6}'); -- OK
+INSERT INTO F1 VALUES (3, '{0,3,6,9,0,3,6,9,0,0,0,0,9,9}'); -- OK (multiple occurrences)
+INSERT INTO F1 VALUES (4, '{0,2,4}'); -- OK (2 is not present)
+INSERT INTO F1 VALUES (5, '{0,NULL,5}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+
+-- Test with TABLE declaration of a two-dim ELEMENT foreign key constraint (FAILS)
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[],
+ FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y)
+);
+
+
+-- Test with two-dim ELEMENT foreign key after TABLE population
+CREATE TABLE F1 (
+ x INTEGER[] PRIMARY KEY, y INTEGER[]
+);
+INSERT INTO F1 VALUES ('{1}', '{0,1,2,3,4,5}'); -- OK
+INSERT INTO F1 VALUES ('{1,2}', '{0,2,4,6}'); -- OK
+-- Add foreign key (FAILS)
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF x, EACH ELEMENT OF y) REFERENCES DIM1(x, y);
+DROP TABLE F1;
+
+-- Cleanup
+DROP TABLE DIM1;
+
+
+-- Check for potential name conflicts (with internal integrity checks)
+CREATE TABLE x1(x1 int, x2 int, PRIMARY KEY(x1,x2));
+INSERT INTO x1 VALUES
+ (1,4),
+ (1,5),
+ (2,4),
+ (2,5),
+ (3,6),
+ (3,7)
+;
+CREATE TABLE x2(x1 int[], x2 int, FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6); -- FAILS
+DROP TABLE x2;
+CREATE TABLE x2(x1 int[], x2 int);
+INSERT INTO x2 VALUES ('{1,2}',4);
+INSERT INTO x2 VALUES ('{1,3}',6);
+ALTER TABLE x2 ADD CONSTRAINT fk_const FOREIGN KEY(EACH ELEMENT OF x1, x2) REFERENCES x1; -- FAILS
+DROP TABLE x2;
+DROP TABLE x1;
+
+
+-- ---------------------------------------
+-- Multi-dimensional "ELEMENT" foreign key tests
+-- ---------------------------------------
+
+-- Create DIM1 table with two-column primary key
+CREATE TABLE DIM1 (X INTEGER NOT NULL PRIMARY KEY,
+ CODE TEXT NOT NULL UNIQUE);
+-- Populate DIM1 table pairs
+INSERT INTO DIM1 SELECT t, 'DIM1-' || lpad(t::TEXT, 2, '0')
+ FROM (SELECT generate_series(1, 10)) x(t);
+
+-- Test with TABLE declaration of an element foreign key constraint (NO ACTION)
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3], FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+UPDATE F1 SET SLOTS = '{{NULL, 1, NULL}, {NULL, NULL, 3}, {7, 8, 10}}' WHERE ID = 1; -- OK
+UPDATE F1 SET SLOTS = '{{100, 100, 100}, {NULL, NULL, 20}, {7, 8, 10}}' WHERE ID = 1; -- FAILS
+DROP TABLE F1;
+
+-- Test with postponed foreign key
+CREATE TABLE F1 (
+ ID SERIAL PRIMARY KEY,
+ SLOTS INTEGER[3][3]
+);
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 3}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}'); -- OK
+INSERT INTO F1(SLOTS) VALUES ('{1, 2, 3, 4, 5, 6, 7, 8, 9}'); -- OK
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- FAILS
+DELETE FROM F1 WHERE ID = 2; -- REMOVE ISSUE
+ALTER TABLE F1 ADD FOREIGN KEY (EACH ELEMENT OF SLOTS) REFERENCES DIM1; -- NOW OK
+INSERT INTO F1(SLOTS) VALUES ('{{NULL, 1, NULL}, {NULL, NULL, 11}, {NULL, NULL, 6}}'); -- FAILS
+DROP TABLE F1;
+
+-- Leave tables in the database
+CREATE TABLE PKTABLEFORELEMENTFK ( ptest1 int PRIMARY KEY, ptest2 text );
+CREATE TABLE FKTABLEFORELEMENTFK ( ftest1 int[], FOREIGN KEY (EACH ELEMENT OF ftest1) REFERENCES PKTABLEFORELEMENTFK, ftest2 int );
+
+-- Check ALTER TABLE ALTER TYPE
+ALTER TABLE FKTABLEFORELEMENTFK ALTER FTEST1 TYPE INT[];
I have written some benchmark test.
With two tables a PK table with 5 rows and an FK table with growing row
count.
Once triggering an RI check
at 10 rows,
100 rows,
1,000 rows,
10,000 rows,
100,000 rows and
1,000,000 rows
Please find the graph with the findings attached below
Attachments:
FK Benchmark.jpgimage/jpeg; name="FK Benchmark.jpg"Download
����0Exif MM * # � � �( 1 �2 ��i �
�� '
�� 'Adobe Photoshop CS6 (Windows) 2017:07:27 02:24:47 � 0221� � ^� n v( ~ � H H ���� Adobe_CM �� Adobe d� �� �
�� W �"