Re: pg_get_constraintdef() doesn't always give an equal constraint - Mailing list pgsql-bugs
From | Tom Lane |
---|---|
Subject | Re: pg_get_constraintdef() doesn't always give an equal constraint |
Date | |
Msg-id | 14244.1427569696@sss.pgh.pa.us Whole thread Raw |
In response to | Re: pg_get_constraintdef() doesn't always give an equal constraint (Jeff Davis <pgsql@j-davis.com>) |
Responses |
Re: pg_get_constraintdef() doesn't always give an equal
constraint
|
List | pgsql-bugs |
Jeff Davis <pgsql@j-davis.com> writes: > On Mon, Mar 23, 2015 at 7:32 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote: >> I don't see any simple way around that except to dump using the syntax >> '10.1'::double precision > There is a similar problem related to NUMERIC: > '1'::numeric is dumped as > 1::numeric > which introduces a cast as well. There's also a problem with negative > constants, because it introduces parenthesis instead of single quotes: > '-1'::numeric > is dumped as > (-1)::numeric Yeah. In general, this code was trying to produce something nice-looking and semantically equivalent, but not necessarily something that would re-parse as the exact same Const node. > This bug is pretty old and nobody has complained about it before. > Let's just figure out a good unambiguous representation of float and > numeric literals, and then backport it. Anyone who cares enough to fix > this issue can upgrade to the latest point release on the old version, > and then dump/reload. > For numeric, I think appending ".0" (if it's an integral value) is the > easiest, prettiest, and least-surprising. ... and wrong, because that would affect the dscale. I don't think we want this code editorializing on the printed format in any case. > For floats, we can either > use the single-quotes and type annotation, or we can come up with > something new like appending an "f" to the value. I cannot see back-porting anything as invasive as changing the lexer. Basically, I think we have to change ruleutils so that it quotes anything that wouldn't be seen as a simple integer or numeric constant by the lexer+grammar. The attached patch does this; the regression test changes illustrate what's going to happen to the output if we do this. Looking at the changes, I'm not 100% convinced we want to back-patch. As you say, nobody's complained of this problem before, and I'm worried that people will see the output changes as a bigger deal than the issue we're trying to fix. Thoughts? regards, tom lane (PS: I've not checked contrib or pl tests, so this patch may be incomplete as far as expected-output changes go.) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 28e1acf..4672cf5 100644 *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 15,20 **** --- 15,21 ---- */ #include "postgres.h" + #include <ctype.h> #include <unistd.h> #include <fcntl.h> *************** get_const_expr(Const *constval, deparse_ *** 8018,8025 **** Oid typoutput; bool typIsVarlena; char *extval; ! bool isfloat = false; ! bool needlabel; if (constval->constisnull) { --- 8019,8025 ---- Oid typoutput; bool typIsVarlena; char *extval; ! bool needlabel = false; if (constval->constisnull) { *************** get_const_expr(Const *constval, deparse_ *** 8045,8084 **** switch (constval->consttype) { - case INT2OID: case INT4OID: ! case INT8OID: ! case OIDOID: ! case FLOAT4OID: ! case FLOAT8OID: case NUMERICOID: { ! /* ! * These types are printed without quotes unless they contain ! * values that aren't accepted by the scanner unquoted (e.g., ! * 'NaN'). Note that strtod() and friends might accept NaN, ! * so we can't use that to test. ! * ! * In reality we only need to defend against infinity and NaN, ! * so we need not get too crazy about pattern matching here. ! * ! * There is a special-case gotcha: if the constant is signed, ! * we need to parenthesize it, else the parser might see a ! * leading plus/minus as binding less tightly than adjacent ! * operators --- particularly, the cast that we might attach ! * below. ! */ ! if (strspn(extval, "0123456789+-eE.") == strlen(extval)) ! { ! if (extval[0] == '+' || extval[0] == '-') ! appendStringInfo(buf, "(%s)", extval); ! else ! appendStringInfoString(buf, extval); ! if (strcspn(extval, "eE.") != strlen(extval)) ! isfloat = true; /* it looks like a float */ ! } ! else ! appendStringInfo(buf, "'%s'", extval); } break; --- 8045,8086 ---- switch (constval->consttype) { case INT4OID: ! ! /* ! * INT4 can be printed without any decoration, unless it is ! * negative; in that case print it as '-nnn'::integer to ensure ! * that the output will re-parse as a constant, not as a constant ! * plus operator. In most cases we could get away with printing ! * (-nnn) instead, because of the way that gram.y handles negative ! * literals; but that doesn't work for INT_MIN, and it doesn't ! * seem that much prettier anyway. ! */ ! if (extval[0] != '-') ! appendStringInfoString(buf, extval); ! else ! { ! appendStringInfo(buf, "'%s'", extval); ! needlabel = true; /* we must attach a cast */ ! } ! break; ! case NUMERICOID: + + /* + * NUMERIC can be printed without quotes if it looks like a float + * constant (not an integer, and not Infinity or NaN) and doesn't + * have a leading sign (for the same reason as for INT4). + */ + if (isdigit((unsigned char) extval[0]) && + strcspn(extval, "eE.") != strlen(extval)) { ! appendStringInfoString(buf, extval); ! } ! else ! { ! appendStringInfo(buf, "'%s'", extval); ! needlabel = true; /* we must attach a cast */ } break; *************** get_const_expr(Const *constval, deparse_ *** 8114,8131 **** switch (constval->consttype) { case BOOLOID: - case INT4OID: case UNKNOWNOID: /* These types can be left unlabeled */ needlabel = false; break; case NUMERICOID: /* * Float-looking constants will be typed as numeric, but if * there's a specific typmod we need to show it. */ ! needlabel = !isfloat || (constval->consttypmod >= 0); break; default: needlabel = true; --- 8116,8135 ---- switch (constval->consttype) { case BOOLOID: case UNKNOWNOID: /* These types can be left unlabeled */ needlabel = false; break; + case INT4OID: + /* We determined above whether a label is needed */ + break; case NUMERICOID: /* * Float-looking constants will be typed as numeric, but if * there's a specific typmod we need to show it. */ ! needlabel |= (constval->consttypmod >= 0); break; default: needlabel = true; diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index dfae84e..0391b8e 100644 *** a/src/test/regress/expected/equivclass.out --- b/src/test/regress/expected/equivclass.out *************** set enable_mergejoin = off; *** 104,114 **** -- explain (costs off) select * from ec0 where ff = f1 and f1 = '42'::int8; ! QUERY PLAN ! ---------------------------------- Index Scan using ec0_pkey on ec0 ! Index Cond: (ff = 42::bigint) ! Filter: (f1 = 42::bigint) (3 rows) explain (costs off) --- 104,114 ---- -- explain (costs off) select * from ec0 where ff = f1 and f1 = '42'::int8; ! QUERY PLAN ! ----------------------------------- Index Scan using ec0_pkey on ec0 ! Index Cond: (ff = '42'::bigint) ! Filter: (f1 = '42'::bigint) (3 rows) explain (costs off) *************** explain (costs off) *** 139,150 **** explain (costs off) select * from ec1, ec2 where ff = x1 and ff = '42'::int8; ! QUERY PLAN ! --------------------------------------------------------------- Nested Loop Join Filter: (ec1.ff = ec2.x1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint)) -> Seq Scan on ec2 (5 rows) --- 139,150 ---- explain (costs off) select * from ec1, ec2 where ff = x1 and ff = '42'::int8; ! QUERY PLAN ! ------------------------------------------------------------------- Nested Loop Join Filter: (ec1.ff = ec2.x1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: ((ff = '42'::bigint) AND (ff = '42'::bigint)) -> Seq Scan on ec2 (5 rows) *************** explain (costs off) *** 161,174 **** explain (costs off) select * from ec1, ec2 where ff = x1 and '42'::int8 = x1; ! QUERY PLAN ! ---------------------------------------- Nested Loop Join Filter: (ec1.ff = ec2.x1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) -> Seq Scan on ec2 ! Filter: (42::bigint = x1) (6 rows) explain (costs off) --- 161,174 ---- explain (costs off) select * from ec1, ec2 where ff = x1 and '42'::int8 = x1; ! QUERY PLAN ! ----------------------------------------- Nested Loop Join Filter: (ec1.ff = ec2.x1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) -> Seq Scan on ec2 ! Filter: ('42'::bigint = x1) (6 rows) explain (costs off) *************** explain (costs off) *** 210,216 **** ----------------------------------------------------- Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) --- 210,216 ---- ----------------------------------------------------- Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) *************** explain (costs off) *** 229,248 **** union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1; ! QUERY PLAN ! --------------------------------------------------------------- Nested Loop Join Filter: ((((ec1_1.ff + 2) + 1)) = ec1.f1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: ((ff = 42::bigint) AND (ff = 42::bigint)) Filter: (ff = f1) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 ! Index Cond: (((ff + 2) + 1) = 42::bigint) -> Index Scan using ec1_expr3 on ec1 ec1_2 ! Index Cond: (((ff + 3) + 1) = 42::bigint) -> Index Scan using ec1_expr4 on ec1 ec1_3 ! Index Cond: ((ff + 4) = 42::bigint) (12 rows) explain (costs off) --- 229,248 ---- union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8 and ec1.ff = ec1.f1; ! QUERY PLAN ! ------------------------------------------------------------------- Nested Loop Join Filter: ((((ec1_1.ff + 2) + 1)) = ec1.f1) -> Index Scan using ec1_pkey on ec1 ! Index Cond: ((ff = '42'::bigint) AND (ff = '42'::bigint)) Filter: (ff = f1) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 ! Index Cond: (((ff + 2) + 1) = '42'::bigint) -> Index Scan using ec1_expr3 on ec1 ec1_2 ! Index Cond: (((ff + 3) + 1) = '42'::bigint) -> Index Scan using ec1_expr4 on ec1 ec1_3 ! Index Cond: ((ff + 4) = '42'::bigint) (12 rows) explain (costs off) *************** explain (costs off) *** 265,271 **** Nested Loop -> Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) --- 265,271 ---- Nested Loop -> Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) *************** explain (costs off) *** 321,327 **** -> Sort Sort Key: ec1.f1 USING < -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) (20 rows) -- check partially indexed scan --- 321,327 ---- -> Sort Sort Key: ec1.f1 USING < -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) (20 rows) -- check partially indexed scan *************** explain (costs off) *** 341,347 **** ----------------------------------------------------- Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) --- 341,347 ---- ----------------------------------------------------- Nested Loop -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) -> Append -> Index Scan using ec1_expr2 on ec1 ec1_1 Index Cond: (((ff + 2) + 1) = ec1.f1) *************** explain (costs off) *** 378,383 **** -> Sort Sort Key: ec1.f1 USING < -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = 42::bigint) (14 rows) --- 378,383 ---- -> Sort Sort Key: ec1.f1 USING < -> Index Scan using ec1_pkey on ec1 ! Index Cond: (ff = '42'::bigint) (14 rows) diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 57fc910..046b085 100644 *** a/src/test/regress/expected/join.out --- b/src/test/regress/expected/join.out *************** SELECT qq, unique1 *** 2544,2559 **** ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2 USING (qq) INNER JOIN tenk1 c ON qq = unique2; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------- Nested Loop -> Hash Full Join ! Hash Cond: (COALESCE(a.q1, 0::bigint) = COALESCE(b.q2, (-1)::bigint)) -> Seq Scan on int8_tbl a -> Hash -> Seq Scan on int8_tbl b -> Index Scan using tenk1_unique2 on tenk1 c ! Index Cond: (unique2 = COALESCE((COALESCE(a.q1, 0::bigint)), (COALESCE(b.q2, (-1)::bigint)))) (8 rows) SELECT qq, unique1 --- 2544,2559 ---- ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2 USING (qq) INNER JOIN tenk1 c ON qq = unique2; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------- Nested Loop -> Hash Full Join ! Hash Cond: (COALESCE(a.q1, '0'::bigint) = COALESCE(b.q2, '-1'::bigint)) -> Seq Scan on int8_tbl a -> Hash -> Seq Scan on int8_tbl b -> Index Scan using tenk1_unique2 on tenk1 c ! Index Cond: (unique2 = COALESCE((COALESCE(a.q1, '0'::bigint)), (COALESCE(b.q2, '-1'::bigint)))) (8 rows) SELECT qq, unique1 *************** select * from *** 3003,3012 **** ) ss where fault = 122 order by fault; ! QUERY PLAN ! ----------------------------------------------------------------- Nested Loop Left Join ! Filter: ((COALESCE(tenk1.unique1, (-1)) + int8_tbl.q1) = 122) -> Seq Scan on int8_tbl -> Index Scan using tenk1_unique2 on tenk1 Index Cond: (int8_tbl.q2 = unique2) --- 3003,3012 ---- ) ss where fault = 122 order by fault; ! QUERY PLAN ! -------------------------------------------------------------------------- Nested Loop Left Join ! Filter: ((COALESCE(tenk1.unique1, '-1'::integer) + int8_tbl.q1) = 122) -> Seq Scan on int8_tbl -> Index Scan using tenk1_unique2 on tenk1 Index Cond: (int8_tbl.q2 = unique2) *************** explain (verbose, costs off) *** 4012,4025 **** select * from int8_tbl a left join lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1; ! QUERY PLAN ! ---------------------------------------------------------------- Nested Loop Left Join ! Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, 42::bigint)) -> Seq Scan on public.int8_tbl a Output: a.q1, a.q2 -> Seq Scan on public.int8_tbl b ! Output: b.q1, b.q2, COALESCE(a.q2, 42::bigint) Filter: (a.q2 = b.q1) (7 rows) --- 4012,4025 ---- select * from int8_tbl a left join lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1; ! QUERY PLAN ! ------------------------------------------------------------------ Nested Loop Left Join ! Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, '42'::bigint)) -> Seq Scan on public.int8_tbl a Output: a.q1, a.q2 -> Seq Scan on public.int8_tbl b ! Output: b.q1, b.q2, COALESCE(a.q2, '42'::bigint) Filter: (a.q2 = b.q1) (7 rows) *************** select * from *** 4235,4266 **** lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 ) on c.q2 = ss2.q1, lateral (select ss2.y offset 0) ss3; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Nested Loop ! Output: c.q1, c.q2, a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint)), d.q1, (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)),((COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))) -> Hash Right Join ! Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)),d.q2)) Hash Cond: (d.q1 = c.q2) -> Nested Loop ! Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)) -> Hash Left Join ! Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint)) Hash Cond: (a.q2 = b.q1) -> Seq Scan on public.int8_tbl a Output: a.q1, a.q2 -> Hash ! Output: b.q1, (COALESCE(b.q2, 42::bigint)) -> Seq Scan on public.int8_tbl b ! Output: b.q1, COALESCE(b.q2, 42::bigint) -> Seq Scan on public.int8_tbl d ! Output: d.q1, COALESCE((COALESCE(b.q2, 42::bigint)), d.q2) -> Hash Output: c.q1, c.q2 -> Seq Scan on public.int8_tbl c Output: c.q1, c.q2 -> Result ! Output: (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)) (24 rows) -- case that breaks the old ph_may_need optimization --- 4235,4266 ---- lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 ) on c.q2 = ss2.q1, lateral (select ss2.y offset 0) ss3; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Nested Loop ! Output: c.q1, c.q2, a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)), d.q1, (COALESCE((COALESCE(b.q2, '42'::bigint)),d.q2)), ((COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))) -> Hash Right Join ! Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)),d.q2)) Hash Cond: (d.q1 = c.q2) -> Nested Loop ! Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)),d.q2)) -> Hash Left Join ! Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)) Hash Cond: (a.q2 = b.q1) -> Seq Scan on public.int8_tbl a Output: a.q1, a.q2 -> Hash ! Output: b.q1, (COALESCE(b.q2, '42'::bigint)) -> Seq Scan on public.int8_tbl b ! Output: b.q1, COALESCE(b.q2, '42'::bigint) -> Seq Scan on public.int8_tbl d ! Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2) -> Hash Output: c.q1, c.q2 -> Seq Scan on public.int8_tbl c Output: c.q1, c.q2 -> Result ! Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) (24 rows) -- case that breaks the old ph_may_need optimization diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out index 54525de..d3a98d1 100644 *** a/src/test/regress/expected/rowtypes.out --- b/src/test/regress/expected/rowtypes.out *************** ERROR: cannot compare dissimilar column *** 284,293 **** explain (costs off) select * from int8_tbl i8 where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------- Seq Scan on int8_tbl i8 ! Filter: (i8.* = ANY (ARRAY[ROW(123::bigint, 456::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl])) (2 rows) select * from int8_tbl i8 --- 284,293 ---- explain (costs off) select * from int8_tbl i8 where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)'); ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------- Seq Scan on int8_tbl i8 ! Filter: (i8.* = ANY (ARRAY[ROW('123'::bigint, '456'::bigint)::int8_tbl, '(4567890123456789,123)'::int8_tbl])) (2 rows) select * from int8_tbl i8 diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index a678e96..016571b 100644 *** a/src/test/regress/expected/union.out --- b/src/test/regress/expected/union.out *************** SELECT * FROM *** 654,666 **** UNION SELECT 2 AS t, 4 AS x) ss WHERE x > 3; ! QUERY PLAN ! ---------------------------------------------------------------------------- Subquery Scan on ss Filter: (ss.x > 3) -> Unique -> Sort ! Sort Key: (1), (((random() * 3::double precision))::integer) -> Append -> Result -> Result --- 654,666 ---- UNION SELECT 2 AS t, 4 AS x) ss WHERE x > 3; ! QUERY PLAN ! ------------------------------------------------------------------------------ Subquery Scan on ss Filter: (ss.x > 3) -> Unique -> Sort ! Sort Key: (1), (((random() * '3'::double precision))::integer) -> Append -> Result -> Result diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out index 6986f47..a31ec34 100644 *** a/src/test/regress/expected/with.out --- b/src/test/regress/expected/with.out *************** SELECT * FROM parent; *** 2083,2090 **** EXPLAIN (VERBOSE, COSTS OFF) WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 ) DELETE FROM a USING wcte WHERE aa = q2; ! QUERY PLAN ! ------------------------------------------------ Delete on public.a Delete on public.a Delete on public.b --- 2083,2090 ---- EXPLAIN (VERBOSE, COSTS OFF) WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 ) DELETE FROM a USING wcte WHERE aa = q2; ! QUERY PLAN ! ---------------------------------------------------- Delete on public.a Delete on public.a Delete on public.b *************** DELETE FROM a USING wcte WHERE aa = q2; *** 2094,2100 **** -> Insert on public.int8_tbl Output: int8_tbl.q2 -> Result ! Output: 42::bigint, 47::bigint -> Nested Loop Output: a.ctid, wcte.* Join Filter: (a.aa = wcte.q2) --- 2094,2100 ---- -> Insert on public.int8_tbl Output: int8_tbl.q2 -> Result ! Output: '42'::bigint, '47'::bigint -> Nested Loop Output: a.ctid, wcte.* Join Filter: (a.aa = wcte.q2)
pgsql-bugs by date: