Commit e3b01174 authored by Tom Lane's avatar Tom Lane

Implement comparison of generic records (composite types), and invent a

pseudo-type record[] to represent arrays of possibly-anonymous composite
types.  Since composite datums carry their own type identification, no
extra knowledge is needed at the array level.

The main reason for doing this right now is that it is necessary to support
the general case of detection of cycles in recursive queries: if you need to
compare more than one column to detect a cycle, you need to compare a ROW()
to an array built from ROW()s, at least if you want to do it as the spec
suggests.  Add some documentation and regression tests concerning the cycle
detection issue.
parent d6dfa1e6
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.448 2008/10/03 07:33:08 heikki Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.449 2008/10/13 16:25:19 tgl Exp $ -->
<chapter id="functions">
<title>Functions and Operators</title>
......@@ -10667,6 +10667,20 @@ AND
be either true or false, never null.
</para>
<note>
<para>
The SQL specification requires row-wise comparison to return NULL if the
result depends on comparing two NULL values or a NULL and a non-NULL.
<productname>PostgreSQL</productname> does this only when comparing the
results of two row constructors or comparing a row constructor to the
output of a subquery (as in <xref linkend="functions-subquery">).
In other contexts where two composite-type values are compared, two
NULL field values are considered equal, and a NULL is considered larger
than a non-NULL. This is necessary in order to have consistent sorting
and indexing behavior for composite types.
</para>
</note>
</sect2>
</sect1>
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.47 2008/10/07 19:27:03 tgl Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/queries.sgml,v 1.48 2008/10/13 16:25:19 tgl Exp $ -->
<chapter id="queries">
<title>Queries</title>
......@@ -1604,8 +1604,85 @@ GROUP BY sub_part
the recursive part of the query will eventually return no tuples,
or else the query will loop indefinitely. Sometimes, using
<literal>UNION</> instead of <literal>UNION ALL</> can accomplish this
by discarding rows that duplicate previous output rows; this catches
cycles that would otherwise repeat. A useful trick for testing queries
by discarding rows that duplicate previous output rows. However, often a
cycle does not involve output rows that are completely duplicate: it may be
necessary to check just one or a few fields to see if the same point has
been reached before. The standard method for handling such situations is
to compute an array of the already-visited values. For example, consider
the following query that searches a table <structname>graph</> using a
<structfield>link</> field:
<programlisting>
WITH RECURSIVE search_graph(id, link, data, depth) AS (
SELECT g.id, g.link, g.data, 1
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1
FROM graph g, search_graph sg
WHERE g.id = sg.link
)
SELECT * FROM search_graph;
</programlisting>
This query will loop if the <structfield>link</> relationships contain
cycles. Because we require a <quote>depth</> output, just changing
<literal>UNION ALL</> to <literal>UNION</> would not eliminate the looping.
Instead we need to recognize whether we have reached the same row again
while following a particular path of links. We add two columns
<structfield>path</> and <structfield>cycle</> to the loop-prone query:
<programlisting>
WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
SELECT g.id, g.link, g.data, 1,
ARRAY[g.id],
false
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1,
path || ARRAY[g.id],
g.id = ANY(path)
FROM graph g, search_graph sg
WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;
</programlisting>
Aside from preventing cycles, the array value is often useful in its own
right as representing the <quote>path</> taken to reach any particular row.
</para>
<para>
In the general case where more than one field needs to be checked to
recognize a cycle, use an array of rows. For example, if we needed to
compare fields <structfield>f1</> and <structfield>f2</>:
<programlisting>
WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
SELECT g.id, g.link, g.data, 1,
ARRAY[ROW(g.f1, g.f2)],
false
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1,
path || ARRAY[ROW(g.f1, g.f2)],
ROW(g.f1, g.f2) = ANY(path)
FROM graph g, search_graph sg
WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;
</programlisting>
</para>
<tip>
<para>
Omit the <literal>ROW()</> syntax in the common case where only one field
needs to be checked to recognize a cycle. This allows a simple array
rather than a composite-type array to be used, gaining efficiency.
</para>
</tip>
<para>
A helpful trick for testing queries
when you are not certain if they might loop is to place a <literal>LIMIT</>
in the parent query. For example, this query would loop forever without
the <literal>LIMIT</>:
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.179 2008/08/25 22:42:32 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.180 2008/10/13 16:25:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -795,7 +795,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
atttype = attform->atttypid;
ReleaseSysCache(atttuple);
}
else if (attribute->expr && IsA(attribute->expr, Var))
else if (attribute->expr && IsA(attribute->expr, Var) &&
((Var *) attribute->expr)->varattno != InvalidAttrNumber)
{
/* Tricky tricky, he wrote (column) ... treat as simple attr */
Var *var = (Var *) attribute->expr;
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.168 2008/10/06 17:39:26 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.169 2008/10/13 16:25:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -46,6 +46,7 @@ static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
CoercionContext ccontext,
CoercionForm cformat,
int location);
static bool is_complex_array(Oid typid);
/*
......@@ -402,6 +403,21 @@ coerce_type(ParseState *pstate, Node *node,
/* NB: we do NOT want a RelabelType here */
return node;
}
#ifdef NOT_USED
if (inputTypeId == RECORDARRAYOID &&
is_complex_array(targetTypeId))
{
/* Coerce record[] to a specific complex array type */
/* not implemented yet ... */
}
#endif
if (targetTypeId == RECORDARRAYOID &&
is_complex_array(inputTypeId))
{
/* Coerce a specific complex array type to record[] */
/* NB: we do NOT want a RelabelType here */
return node;
}
if (typeInheritsFrom(inputTypeId, targetTypeId))
{
/*
......@@ -492,6 +508,23 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids,
ISCOMPLEX(inputTypeId))
continue;
#ifdef NOT_USED /* not implemented yet */
/*
* If input is record[] and target is a composite array type,
* assume we can coerce (may need tighter checking here)
*/
if (inputTypeId == RECORDARRAYOID &&
is_complex_array(targetTypeId))
continue;
#endif
/*
* If input is a composite array type and target is record[], accept
*/
if (targetTypeId == RECORDARRAYOID &&
is_complex_array(inputTypeId))
continue;
/*
* If input is a class type that inherits from target, accept
*/
......@@ -1724,8 +1757,8 @@ IsPreferredType(TYPCATEGORY category, Oid type)
* invokable, no-function-needed pg_cast entry. Also, a domain is always
* binary-coercible to its base type, though *not* vice versa (in the other
* direction, one must apply domain constraint checks before accepting the
* value as legitimate). We also need to special-case the polymorphic
* ANYARRAY type.
* value as legitimate). We also need to special-case various polymorphic
* types.
*
* This function replaces IsBinaryCompatible(), which was an inherently
* symmetric test. Since the pg_cast entries aren't necessarily symmetric,
......@@ -1765,6 +1798,16 @@ IsBinaryCoercible(Oid srctype, Oid targettype)
if (type_is_enum(srctype))
return true;
/* Also accept any composite type as coercible to RECORD */
if (targettype == RECORDOID)
if (ISCOMPLEX(srctype))
return true;
/* Also accept any composite array type as coercible to RECORD[] */
if (targettype == RECORDARRAYOID)
if (is_complex_array(srctype))
return true;
/* Else look in pg_cast */
tuple = SearchSysCache(CASTSOURCETARGET,
ObjectIdGetDatum(srctype),
......@@ -2002,3 +2045,18 @@ find_typmod_coercion_function(Oid typeId,
return result;
}
/*
* is_complex_array
* Is this type an array of composite?
*
* Note: this will not return true for record[]; check for RECORDARRAYOID
* separately if needed.
*/
static bool
is_complex_array(Oid typid)
{
Oid elemtype = get_element_type(typid);
return (OidIsValid(elemtype) && ISCOMPLEX(elemtype));
}
This diff is collapsed.
......@@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.496 2008/10/06 17:39:26 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.497 2008/10/13 16:25:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200810063
#define CATALOG_VERSION_NO 200810131
#endif
......@@ -29,7 +29,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_amop.h,v 1.87 2008/05/27 00:13:09 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_amop.h,v 1.88 2008/10/13 16:25:19 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -470,6 +470,16 @@ DATA(insert ( 397 2277 2277 3 1070 403 ));
DATA(insert ( 397 2277 2277 4 1075 403 ));
DATA(insert ( 397 2277 2277 5 1073 403 ));
/*
* btree record_ops
*/
DATA(insert ( 2994 2249 2249 1 2990 403 ));
DATA(insert ( 2994 2249 2249 2 2992 403 ));
DATA(insert ( 2994 2249 2249 3 2988 403 ));
DATA(insert ( 2994 2249 2249 4 2993 403 ));
DATA(insert ( 2994 2249 2249 5 2991 403 ));
/*
* btree uuid_ops
*/
......
......@@ -22,7 +22,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_amproc.h,v 1.73 2008/05/27 00:13:09 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_amproc.h,v 1.74 2008/10/13 16:25:19 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -111,6 +111,7 @@ DATA(insert ( 1986 19 19 1 359 ));
DATA(insert ( 1988 1700 1700 1 1769 ));
DATA(insert ( 1989 26 26 1 356 ));
DATA(insert ( 1991 30 30 1 404 ));
DATA(insert ( 2994 2249 2249 1 2987 ));
DATA(insert ( 1994 25 25 1 360 ));
DATA(insert ( 1996 1083 1083 1 1107 ));
DATA(insert ( 2000 1266 1266 1 1358 ));
......
......@@ -28,7 +28,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.83 2008/09/15 18:43:41 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_opclass.h,v 1.84 2008/10/13 16:25:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -137,6 +137,7 @@ DATA(insert OID = 1981 ( 403 oid_ops PGNSP PGUID 1989 26 t 0 ));
DATA(insert ( 405 oid_ops PGNSP PGUID 1990 26 t 0 ));
DATA(insert ( 403 oidvector_ops PGNSP PGUID 1991 30 t 0 ));
DATA(insert ( 405 oidvector_ops PGNSP PGUID 1992 30 t 0 ));
DATA(insert ( 403 record_ops PGNSP PGUID 2994 2249 t 0 ));
DATA(insert ( 403 text_ops PGNSP PGUID 1994 25 t 0 ));
DATA(insert ( 405 text_ops PGNSP PGUID 1995 25 t 0 ));
DATA(insert ( 403 time_ops PGNSP PGUID 1996 1083 t 0 ));
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_operator.h,v 1.163 2008/09/19 19:03:40 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_operator.h,v 1.164 2008/10/13 16:25:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -933,6 +933,14 @@ DATA(insert OID = 3694 ( "<@" PGNSP PGUID b f f 3615 3615 16 3693 0 tsq_m
DATA(insert OID = 3762 ( "@@" PGNSP PGUID b f f 25 25 16 0 0 ts_match_tt contsel contjoinsel ));
DATA(insert OID = 3763 ( "@@" PGNSP PGUID b f f 25 3615 16 0 0 ts_match_tq contsel contjoinsel ));
/* generic record comparison operators */
DATA(insert OID = 2988 ( "=" PGNSP PGUID b t f 2249 2249 16 2988 2989 record_eq eqsel eqjoinsel ));
DATA(insert OID = 2989 ( "<>" PGNSP PGUID b f f 2249 2249 16 2989 2988 record_ne neqsel neqjoinsel ));
DATA(insert OID = 2990 ( "<" PGNSP PGUID b f f 2249 2249 16 2991 2993 record_lt scalarltsel scalarltjoinsel ));
DATA(insert OID = 2991 ( ">" PGNSP PGUID b f f 2249 2249 16 2990 2992 record_gt scalargtsel scalargtjoinsel ));
DATA(insert OID = 2992 ( "<=" PGNSP PGUID b f f 2249 2249 16 2993 2991 record_le scalarltsel scalarltjoinsel ));
DATA(insert OID = 2993 ( ">=" PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel ));
/*
* function prototypes
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_opfamily.h,v 1.9 2008/05/27 00:13:09 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_opfamily.h,v 1.10 2008/10/13 16:25:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -94,6 +94,7 @@ DATA(insert OID = 1989 ( 403 oid_ops PGNSP PGUID ));
DATA(insert OID = 1990 ( 405 oid_ops PGNSP PGUID ));
DATA(insert OID = 1991 ( 403 oidvector_ops PGNSP PGUID ));
DATA(insert OID = 1992 ( 405 oidvector_ops PGNSP PGUID ));
DATA(insert OID = 2994 ( 403 record_ops PGNSP PGUID ));
DATA(insert OID = 1994 ( 403 text_ops PGNSP PGUID ));
#define TEXT_BTREE_FAM_OID 1994
DATA(insert OID = 1995 ( 405 text_ops PGNSP PGUID ));
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.518 2008/10/06 13:05:37 mha Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.519 2008/10/13 16:25:20 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
......@@ -4560,6 +4560,22 @@ DESCR("get set of in-progress txids in snapshot");
DATA(insert OID = 2948 ( txid_visible_in_snapshot PGNSP PGUID 12 1 0 0 f f t f i 2 16 "20 2970" _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ ));
DESCR("is txid visible in snapshot?");
/* record comparison */
DATA(insert OID = 2981 ( record_eq PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_eq _null_ _null_ _null_ ));
DESCR("record equal");
DATA(insert OID = 2982 ( record_ne PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_ne _null_ _null_ _null_ ));
DESCR("record not equal");
DATA(insert OID = 2983 ( record_lt PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_lt _null_ _null_ _null_ ));
DESCR("record less than");
DATA(insert OID = 2984 ( record_gt PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_gt _null_ _null_ _null_ ));
DESCR("record greater than");
DATA(insert OID = 2985 ( record_le PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_le _null_ _null_ _null_ ));
DESCR("record less than or equal");
DATA(insert OID = 2986 ( record_ge PGNSP PGUID 12 1 0 0 f f t f i 2 16 "2249 2249" _null_ _null_ _null_ record_ge _null_ _null_ _null_ ));
DESCR("record greater than or equal");
DATA(insert OID = 2987 ( btrecordcmp PGNSP PGUID 12 1 0 0 f f t f i 2 23 "2249 2249" _null_ _null_ _null_ btrecordcmp _null_ _null_ _null_ ));
DESCR("btree less-equal-greater");
/*
* Symbolic values for provolatile column: these indicate whether the result
......
......@@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.200 2008/09/25 03:28:56 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_type.h,v 1.201 2008/10/13 16:25:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
......@@ -597,8 +597,10 @@ DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a
* but there is now support for it in records and arrays. Perhaps we should
* just treat it as a regular base type?
*/
DATA(insert OID = 2249 ( record PGNSP PGUID -1 f p P f t \054 0 0 0 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
DATA(insert OID = 2249 ( record PGNSP PGUID -1 f p P f t \054 0 0 2287 record_in record_out record_recv record_send - - - d x f 0 -1 0 _null_ _null_ ));
#define RECORDOID 2249
DATA(insert OID = 2287 ( _record PGNSP PGUID -1 f p P f t \054 0 2249 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 _null_ _null_ ));
#define RECORDARRAYOID 2287
DATA(insert OID = 2275 ( cstring PGNSP PGUID -2 f p P f t \054 0 0 1263 cstring_in cstring_out cstring_recv cstring_send - - - c p f 0 -1 0 _null_ _null_ ));
#define CSTRINGOID 2275
DATA(insert OID = 2276 ( any PGNSP PGUID 4 t p P f t \054 0 0 0 any_in any_out - - - - - i p f 0 -1 0 _null_ _null_ ));
......
......@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.323 2008/10/06 20:29:38 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.324 2008/10/13 16:25:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -524,6 +524,13 @@ extern Datum record_in(PG_FUNCTION_ARGS);
extern Datum record_out(PG_FUNCTION_ARGS);
extern Datum record_recv(PG_FUNCTION_ARGS);
extern Datum record_send(PG_FUNCTION_ARGS);
extern Datum record_eq(PG_FUNCTION_ARGS);
extern Datum record_ne(PG_FUNCTION_ARGS);
extern Datum record_lt(PG_FUNCTION_ARGS);
extern Datum record_gt(PG_FUNCTION_ARGS);
extern Datum record_le(PG_FUNCTION_ARGS);
extern Datum record_ge(PG_FUNCTION_ARGS);
extern Datum btrecordcmp(PG_FUNCTION_ARGS);
/* ruleutils.c */
extern Datum pg_get_ruledef(PG_FUNCTION_ARGS);
......
......@@ -269,3 +269,23 @@ select ROW() = ROW();
ERROR: cannot compare rows of zero length
LINE 1: select ROW() = ROW();
^
-- Check ability to create arrays of anonymous rowtypes
select array[ row(1,2), row(3,4), row(5,6) ];
array
---------------------------
{"(1,2)","(3,4)","(5,6)"}
(1 row)
-- Check ability to compare an anonymous row to elements of an array
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
?column?
----------
t
(1 row)
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
?column?
----------
f
(1 row)
......@@ -451,6 +451,54 @@ SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
3 | 7
(2 rows)
--
-- test cycle detection
--
create temp table graph( f int, t int, label text );
insert into graph values
(1, 2, 'arc 1 -> 2'),
(1, 3, 'arc 1 -> 3'),
(2, 3, 'arc 2 -> 3'),
(1, 4, 'arc 1 -> 4'),
(4, 5, 'arc 4 -> 5'),
(5, 1, 'arc 5 -> 1');
with recursive search_graph(f, t, label, path, cycle) as (
select *, array[row(g.f, g.t)], false from graph g
union all
select g.*, path || array[row(g.f, g.t)], row(g.f, g.t) = any(path)
from graph g, search_graph sg
where g.f = sg.t and not cycle
)
select * from search_graph;
f | t | label | path | cycle
---+---+------------+-------------------------------------------+-------
1 | 2 | arc 1 -> 2 | {"(1,2)"} | f
1 | 3 | arc 1 -> 3 | {"(1,3)"} | f
2 | 3 | arc 2 -> 3 | {"(2,3)"} | f
1 | 4 | arc 1 -> 4 | {"(1,4)"} | f
4 | 5 | arc 4 -> 5 | {"(4,5)"} | f
5 | 1 | arc 5 -> 1 | {"(5,1)"} | f
1 | 2 | arc 1 -> 2 | {"(5,1)","(1,2)"} | f
1 | 3 | arc 1 -> 3 | {"(5,1)","(1,3)"} | f
1 | 4 | arc 1 -> 4 | {"(5,1)","(1,4)"} | f
2 | 3 | arc 2 -> 3 | {"(1,2)","(2,3)"} | f
4 | 5 | arc 4 -> 5 | {"(1,4)","(4,5)"} | f
5 | 1 | arc 5 -> 1 | {"(4,5)","(5,1)"} | f
1 | 2 | arc 1 -> 2 | {"(4,5)","(5,1)","(1,2)"} | f
1 | 3 | arc 1 -> 3 | {"(4,5)","(5,1)","(1,3)"} | f
1 | 4 | arc 1 -> 4 | {"(4,5)","(5,1)","(1,4)"} | f
2 | 3 | arc 2 -> 3 | {"(5,1)","(1,2)","(2,3)"} | f
4 | 5 | arc 4 -> 5 | {"(5,1)","(1,4)","(4,5)"} | f
5 | 1 | arc 5 -> 1 | {"(1,4)","(4,5)","(5,1)"} | f
1 | 2 | arc 1 -> 2 | {"(1,4)","(4,5)","(5,1)","(1,2)"} | f
1 | 3 | arc 1 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,3)"} | f
1 | 4 | arc 1 -> 4 | {"(1,4)","(4,5)","(5,1)","(1,4)"} | t
2 | 3 | arc 2 -> 3 | {"(4,5)","(5,1)","(1,2)","(2,3)"} | f
4 | 5 | arc 4 -> 5 | {"(4,5)","(5,1)","(1,4)","(4,5)"} | t
5 | 1 | arc 5 -> 1 | {"(5,1)","(1,4)","(4,5)","(5,1)"} | t
2 | 3 | arc 2 -> 3 | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"} | f
(25 rows)
--
-- test multiple WITH queries
--
......
......@@ -113,3 +113,10 @@ order by thousand, tenthous;
select ROW();
select ROW() IS NULL;
select ROW() = ROW();
-- Check ability to create arrays of anonymous rowtypes
select array[ row(1,2), row(3,4), row(5,6) ];
-- Check ability to compare an anonymous row to elements of an array
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
......@@ -250,6 +250,28 @@ SELECT t1.id, count(t2.*) FROM t AS t1 JOIN t AS t2 ON
GROUP BY t1.id
ORDER BY t1.id;
--
-- test cycle detection
--
create temp table graph( f int, t int, label text );
insert into graph values
(1, 2, 'arc 1 -> 2'),
(1, 3, 'arc 1 -> 3'),
(2, 3, 'arc 2 -> 3'),
(1, 4, 'arc 1 -> 4'),
(4, 5, 'arc 4 -> 5'),
(5, 1, 'arc 5 -> 1');
with recursive search_graph(f, t, label, path, cycle) as (
select *, array[row(g.f, g.t)], false from graph g
union all
select g.*, path || array[row(g.f, g.t)], row(g.f, g.t) = any(path)
from graph g, search_graph sg
where g.f = sg.t and not cycle
)
select * from search_graph;
--
-- test multiple WITH queries
--
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment