Hi,
EXPLAIN command doesn't show testexpr. Sometimes it is not easy to
understand a query plan. That I mean:
CREATE TABLE a (x integer, y integer);
EXPLAIN (COSTS OFF, VERBOSE) SELECT x, y FROM a upper
WHERE y IN (SELECT y FROM a WHERE upper.y = x);
EXPLAIN (COSTS OFF, VERBOSE) SELECT x, y FROM a upper
WHERE x+y IN (SELECT y FROM a WHERE upper.y = x);
These two explains have the same representation:
Seq Scan on public.a upper
Output: upper.x, upper.y
Filter: (SubPlan 1)
SubPlan 1
-> Seq Scan on public.a
Output: a.y
Filter: (upper.y = a.x)
It is a bit annoying when you don't have original query or don't trust
competence of a user who sent you this explain.
In attachment - patch which solves this problem. I'm not completely sure
that this option really needed and patch presents a proof of concept only.
--
regards,
Andrey Lepikhov
Postgres Professional
From db440802d84a96a98d0fb30672232e81e10c4598 Mon Sep 17 00:00:00 2001
From: Andrey Lepikhov <a.lepik...@postgrespro.ru>
Date: Tue, 24 Aug 2021 10:59:00 +0500
Subject: [PATCH] Improve EXPLAIN presentation
---
src/backend/utils/adt/ruleutils.c | 101 +++++++++++-------
src/test/regress/expected/insert_conflict.out | 2 +-
src/test/regress/expected/join.out | 2 +-
src/test/regress/expected/rowsecurity.out | 6 +-
src/test/regress/expected/select_parallel.out | 12 +--
src/test/regress/expected/subselect.out | 40 +++----
src/test/regress/expected/updatable_views.out | 24 ++---
7 files changed, 106 insertions(+), 81 deletions(-)
diff --git a/src/backend/utils/adt/ruleutils.c
b/src/backend/utils/adt/ruleutils.c
index 4df8cc5abf..1c25769633 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -455,7 +455,10 @@ static void get_const_expr(Const *constval,
deparse_context *context,
int showtype);
static void get_const_collation(Const *constval, deparse_context *context);
static void simple_quote_literal(StringInfo buf, const char *val);
+static void get_subplan_expr(SubPlan *subplan, deparse_context *context);
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
+static bool get_subselect_expr(SubLinkType subLinkType, Node *testexpr,
+ deparse_context
*context);
static void get_tablefunc(TableFunc *tf, deparse_context *context,
bool showimplicit);
static void get_from_clause(Query *query, const char *prefix,
@@ -8536,20 +8539,7 @@ get_rule_expr(Node *node, deparse_context *context,
break;
case T_SubPlan:
- {
- SubPlan *subplan = (SubPlan *) node;
-
- /*
- * We cannot see an already-planned subplan in
rule deparsing,
- * only while EXPLAINing a query plan. We
don't try to
- * reconstruct the original SQL, just reference
the subplan
- * that appears elsewhere in EXPLAIN's result.
- */
- if (subplan->useHashTable)
- appendStringInfo(buf, "(hashed %s)",
subplan->plan_name);
- else
- appendStringInfo(buf, "(%s)",
subplan->plan_name);
- }
+ get_subplan_expr((SubPlan *) node, context);
break;
case T_AlternativeSubPlan:
@@ -10358,6 +10348,31 @@ simple_quote_literal(StringInfo buf, const char *val)
}
+static void
+get_subplan_expr(SubPlan *subplan, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+
+ if (subplan->testexpr)
+ appendStringInfoChar(buf, '(');
+
+ (void) get_subselect_expr(subplan->subLinkType, subplan->testexpr,
context);
+
+ /*
+ * We cannot see an already-planned subplan in rule deparsing,
+ * only while EXPLAINing a query plan. We don't try to
+ * reconstruct the original SQL, just reference the subplan
+ * that appears elsewhere in EXPLAIN's result.
+ */
+ if (subplan->useHashTable)
+ appendStringInfo(buf, "(hashed %s)", subplan->plan_name);
+ else
+ appendStringInfo(buf, "(%s)", subplan->plan_name);
+
+ if (subplan->testexpr)
+ appendStringInfoChar(buf, ')');
+}
+
/* ----------
* get_sublink_expr - Parse back a sublink
* ----------
@@ -10367,14 +10382,35 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
{
StringInfo buf = context->buf;
Query *query = (Query *) (sublink->subselect);
- char *opname = NULL;
- bool need_paren;
+ bool need_paren = true;
if (sublink->subLinkType == ARRAY_SUBLINK)
appendStringInfoString(buf, "ARRAY(");
else
appendStringInfoChar(buf, '(');
+ need_paren = get_subselect_expr(sublink->subLinkType,
sublink->testexpr, context);
+
+ if (need_paren)
+ appendStringInfoChar(buf, '(');
+
+ get_query_def(query, buf, context->namespaces, NULL,
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
+
+ if (need_paren)
+ appendStringInfoString(buf, "))");
+ else if (query)
+ appendStringInfoChar(buf, ')');
+}
+
+static bool
+get_subselect_expr(SubLinkType subLinkType, Node *testexpr, deparse_context
*context)
+{
+ StringInfo buf = context->buf;
+ char *opname = NULL;
+ bool need_paren;
+
/*
* Note that we print the name of only the first operator, when there
are
* multiple combining operators. This is an approximation that could go
@@ -10382,19 +10418,19 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
* operators, etc) but there is not a whole lot we can do about it,
since
* the syntax allows only one operator to be shown.
*/
- if (sublink->testexpr)
+ if (testexpr)
{
- if (IsA(sublink->testexpr, OpExpr))
+ if (IsA(testexpr, OpExpr))
{
/* single combining operator */
- OpExpr *opexpr = (OpExpr *) sublink->testexpr;
+ OpExpr *opexpr = (OpExpr *) testexpr;
get_rule_expr(linitial(opexpr->args), context, true);
opname = generate_operator_name(opexpr->opno,
exprType(linitial(opexpr->args)),
exprType(lsecond(opexpr->args)));
}
- else if (IsA(sublink->testexpr, BoolExpr))
+ else if (IsA(testexpr, BoolExpr))
{
/* multiple combining operators, = or <> cases */
char *sep;
@@ -10402,7 +10438,7 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
appendStringInfoChar(buf, '(');
sep = "";
- foreach(l, ((BoolExpr *) sublink->testexpr)->args)
+ foreach(l, ((BoolExpr *) testexpr)->args)
{
OpExpr *opexpr = lfirst_node(OpExpr, l);
@@ -10416,10 +10452,10 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
}
appendStringInfoChar(buf, ')');
}
- else if (IsA(sublink->testexpr, RowCompareExpr))
+ else if (IsA(testexpr, RowCompareExpr))
{
/* multiple combining operators, < <= > >= cases */
- RowCompareExpr *rcexpr = (RowCompareExpr *)
sublink->testexpr;
+ RowCompareExpr *rcexpr = (RowCompareExpr *) testexpr;
appendStringInfoChar(buf, '(');
get_rule_expr((Node *) rcexpr->largs, context, true);
@@ -10430,12 +10466,12 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
}
else
elog(ERROR, "unrecognized testexpr type: %d",
- (int) nodeTag(sublink->testexpr));
+ (int) nodeTag(testexpr));
}
need_paren = true;
- switch (sublink->subLinkType)
+ switch (subLinkType)
{
case EXISTS_SUBLINK:
appendStringInfoString(buf, "EXISTS ");
@@ -10465,21 +10501,10 @@ get_sublink_expr(SubLink *sublink, deparse_context
*context)
case CTE_SUBLINK: /* shouldn't occur in a SubLink
*/
default:
elog(ERROR, "unrecognized sublink type: %d",
- (int) sublink->subLinkType);
+ (int) subLinkType);
break;
}
-
- if (need_paren)
- appendStringInfoChar(buf, '(');
-
- get_query_def(query, buf, context->namespaces, NULL,
- context->prettyFlags, context->wrapColumn,
- context->indentLevel);
-
- if (need_paren)
- appendStringInfoString(buf, "))");
- else
- appendStringInfoChar(buf, ')');
+ return need_paren;
}
diff --git a/src/test/regress/expected/insert_conflict.out
b/src/test/regress/expected/insert_conflict.out
index 66d8633e3e..8ac7756925 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -50,7 +50,7 @@ explain (costs off) insert into insertconflicttest values(0,
'Crowberry') on con
Insert on insertconflicttest
Conflict Resolution: UPDATE
Conflict Arbiter Indexes: op_index_key, collation_index_key, both_index_key
- Conflict Filter: (SubPlan 1)
+ Conflict Filter: EXISTS (SubPlan 1)
-> Result
SubPlan 1
-> Index Only Scan using both_index_expr_key on insertconflicttest ii
diff --git a/src/test/regress/expected/join.out
b/src/test/regress/expected/join.out
index f3589d0dbb..bfc50f7a75 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -5899,7 +5899,7 @@ lateral (select * from int8_tbl t1,
Filter: (t1.q1 = ss2.q2)
-> Seq Scan on public.int8_tbl t2
Output: t2.q1, t2.q2
- Filter: (SubPlan 3)
+ Filter: (t2.q1 IN (SubPlan 3))
SubPlan 3
-> Result
Output: t3.q2
diff --git a/src/test/regress/expected/rowsecurity.out
b/src/test/regress/expected/rowsecurity.out
index 89397e41f0..1e34c41480 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1440,7 +1440,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
QUERY PLAN
-----------------------------------------------------------
Seq Scan on s1
- Filter: ((hashed SubPlan 1) AND f_leak(b))
+ Filter: ((a IN (hashed SubPlan 1)) AND f_leak(b))
SubPlan 1
-> Seq Scan on s2
Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))
@@ -1462,7 +1462,7 @@ EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
QUERY PLAN
-----------------------------------------------------------
Seq Scan on s1
- Filter: ((hashed SubPlan 1) AND f_leak(b))
+ Filter: ((a IN (hashed SubPlan 1)) AND f_leak(b))
SubPlan 1
-> Seq Scan on s2
Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
@@ -1484,7 +1484,7 @@ EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx,
* FROM s2 WHERE y like
SubPlan 2
-> Limit
-> Seq Scan on s1
- Filter: (hashed SubPlan 1)
+ Filter: (a IN (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on s2 s2_1
Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
diff --git a/src/test/regress/expected/select_parallel.out
b/src/test/regress/expected/select_parallel.out
index 4ea1aa7dfd..bd77033882 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -291,14 +291,14 @@ alter table tenk2 set (parallel_workers = 0);
explain (costs off)
select count(*) from tenk1 where (two, four) not in
(select hundred, thousand from tenk2 where thousand > 100);
- QUERY PLAN
-------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------
Finalize Aggregate
-> Gather
Workers Planned: 4
-> Partial Aggregate
-> Parallel Seq Scan on tenk1
- Filter: (NOT (hashed SubPlan 1))
+ Filter: (NOT ((two, four) IN (hashed SubPlan 1)))
SubPlan 1
-> Seq Scan on tenk2
Filter: (thousand > 100)
@@ -315,10 +315,10 @@ select count(*) from tenk1 where (two, four) not in
explain (costs off)
select * from tenk1 where (unique1 + random())::integer not in
(select ten from tenk2);
- QUERY PLAN
-------------------------------------
+ QUERY PLAN
+---------------------------------------------------------------------------------------------
Seq Scan on tenk1
- Filter: (NOT (hashed SubPlan 1))
+ Filter: (NOT ((((unique1)::double precision + random()))::integer IN
(hashed SubPlan 1)))
SubPlan 1
-> Seq Scan on tenk2
(4 rows)
diff --git a/src/test/regress/expected/subselect.out
b/src/test/regress/expected/subselect.out
index 30615dd6bc..2bc2f0ba56 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -258,7 +258,7 @@ select 1 = all (select (select 1));
QUERY PLAN
-----------------------------------
Result
- Output: (SubPlan 2)
+ Output: (1 = ALL (SubPlan 2))
SubPlan 2
-> Materialize
Output: ($0)
@@ -308,7 +308,7 @@ select * from int4_tbl o where exists
QUERY PLAN
--------------------------------------
Seq Scan on int4_tbl o
- Filter: (SubPlan 1)
+ Filter: EXISTS (SubPlan 1)
SubPlan 1
-> Limit
-> Seq Scan on int4_tbl i
@@ -771,10 +771,10 @@ select * from outer_text where (f1, f2) not in (select *
from inner_text);
--
explain (verbose, costs off)
select 'foo'::text in (select 'bar'::name union all select 'bar'::name);
- QUERY PLAN
--------------------------------------
+ QUERY PLAN
+-----------------------------------------------
Result
- Output: (hashed SubPlan 1)
+ Output: ('foo'::text IN (hashed SubPlan 1))
SubPlan 1
-> Append
-> Result
@@ -815,10 +815,10 @@ language sql as 'select $1::text = $2';
create operator = (procedure=bogus_int8_text_eq, leftarg=int8, rightarg=text);
explain (costs off)
select * from int8_tbl where q1 in (select c1 from inner_text);
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+----------------------------------------------
Seq Scan on int8_tbl
- Filter: (hashed SubPlan 1)
+ Filter: ((q1)::text IN (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on inner_text
(4 rows)
@@ -836,10 +836,10 @@ create or replace function bogus_int8_text_eq(int8, text)
returns boolean
language sql as 'select $1::text = $2 and $1::text = $2';
explain (costs off)
select * from int8_tbl where q1 in (select c1 from inner_text);
- QUERY PLAN
---------------------------------
+ QUERY PLAN
+------------------------------------------------------------
Seq Scan on int8_tbl
- Filter: (hashed SubPlan 1)
+ Filter: (((q1)::text, (q1)::text) IN (hashed SubPlan 1))
SubPlan 1
-> Seq Scan on inner_text
(4 rows)
@@ -860,7 +860,7 @@ select * from int8_tbl where q1 in (select c1 from
inner_text);
QUERY PLAN
--------------------------------------
Seq Scan on int8_tbl
- Filter: (SubPlan 1)
+ Filter: ($0 IN (SubPlan 1))
SubPlan 1
-> Materialize
-> Seq Scan on inner_text
@@ -880,11 +880,11 @@ rollback; -- to get rid of the bogus operator
explain (costs off)
select count(*) from tenk1 t
where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0);
- QUERY PLAN
---------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------
Aggregate
-> Seq Scan on tenk1 t
- Filter: ((hashed SubPlan 2) OR (ten < 0))
+ Filter: ((unique2 IN (hashed SubPlan 2)) OR (ten < 0))
SubPlan 2
-> Index Only Scan using tenk1_unique1 on tenk1 k
(5 rows)
@@ -905,7 +905,7 @@ where (exists(select 1 from tenk1 k where k.unique1 =
t.unique2) or ten < 0)
Aggregate
-> Bitmap Heap Scan on tenk1 t
Recheck Cond: (thousand = 1)
- Filter: ((SubPlan 1) OR (ten < 0))
+ Filter: (EXISTS (SubPlan 1) OR (ten < 0))
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand = 1)
SubPlan 1
@@ -1011,14 +1011,14 @@ where o.ten = 0;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate
- Output: sum((((hashed SubPlan 1)))::integer)
+ Output: sum((((i.ten IN (hashed SubPlan 1))))::integer)
-> Nested Loop
- Output: ((hashed SubPlan 1))
+ Output: ((i.ten IN (hashed SubPlan 1)))
-> Seq Scan on public.onek o
Output: o.unique1, o.unique2, o.two, o.four, o.ten, o.twenty,
o.hundred, o.thousand, o.twothousand, o.fivethous, o.tenthous, o.odd, o.even,
o.stringu1, o.stringu2, o.string4
Filter: (o.ten = 0)
-> Index Scan using onek_unique1 on public.onek i
- Output: (hashed SubPlan 1), random()
+ Output: (i.ten IN (hashed SubPlan 1)), random()
Index Cond: (i.unique1 = o.unique1)
SubPlan 1
-> Seq Scan on public.int4_tbl
@@ -1213,7 +1213,7 @@ select * from int4_tbl where
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Semi Join
Output: int4_tbl.f1
- Join Filter: (CASE WHEN (hashed SubPlan 1) THEN int4_tbl.f1 ELSE
NULL::integer END = b.ten)
+ Join Filter: (CASE WHEN (int4_tbl.f1 IN (hashed SubPlan 1)) THEN
int4_tbl.f1 ELSE NULL::integer END = b.ten)
-> Seq Scan on public.int4_tbl
Output: int4_tbl.f1
-> Seq Scan on public.tenk1 b
diff --git a/src/test/regress/expected/updatable_views.out
b/src/test/regress/expected/updatable_views.out
index cdff914b93..b4e91573c7 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2327,8 +2327,8 @@ SELECT * FROM v1 WHERE a=8;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
- QUERY PLAN
------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------------------------
Update on public.t1
Update on public.t1 t1_1
Update on public.t11 t1_2
@@ -2340,7 +2340,7 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a
< 7 AND a != 6;
-> Index Scan using t1_a_idx on public.t1 t1_1
Output: t1_1.tableoid, t1_1.ctid
Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
- Filter: ((t1_1.a <> 6) AND (SubPlan 1) AND snoop(t1_1.a)
AND leakproof(t1_1.a))
+ Filter: ((t1_1.a <> 6) AND EXISTS (SubPlan 1) AND
snoop(t1_1.a) AND leakproof(t1_1.a))
SubPlan 1
-> Append
-> Seq Scan on public.t12 t12_1
@@ -2350,15 +2350,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND
a < 7 AND a != 6;
-> Index Scan using t11_a_idx on public.t11 t1_2
Output: t1_2.tableoid, t1_2.ctid
Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
- Filter: ((t1_2.a <> 6) AND (SubPlan 1) AND snoop(t1_2.a)
AND leakproof(t1_2.a))
+ Filter: ((t1_2.a <> 6) AND EXISTS (SubPlan 1) AND
snoop(t1_2.a) AND leakproof(t1_2.a))
-> Index Scan using t12_a_idx on public.t12 t1_3
Output: t1_3.tableoid, t1_3.ctid
Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
- Filter: ((t1_3.a <> 6) AND (SubPlan 1) AND snoop(t1_3.a)
AND leakproof(t1_3.a))
+ Filter: ((t1_3.a <> 6) AND EXISTS (SubPlan 1) AND
snoop(t1_3.a) AND leakproof(t1_3.a))
-> Index Scan using t111_a_idx on public.t111 t1_4
Output: t1_4.tableoid, t1_4.ctid
Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
- Filter: ((t1_4.a <> 6) AND (SubPlan 1) AND snoop(t1_4.a)
AND leakproof(t1_4.a))
+ Filter: ((t1_4.a <> 6) AND EXISTS (SubPlan 1) AND
snoop(t1_4.a) AND leakproof(t1_4.a))
(30 rows)
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
@@ -2374,8 +2374,8 @@ SELECT * FROM t1 WHERE a=100; -- Nothing should have been
changed to 100
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
- QUERY PLAN
------------------------------------------------------------------------------------
+ QUERY PLAN
+------------------------------------------------------------------------------------------
Update on public.t1
Update on public.t1 t1_1
Update on public.t11 t1_2
@@ -2387,7 +2387,7 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a
= 8;
-> Index Scan using t1_a_idx on public.t1 t1_1
Output: t1_1.a, t1_1.tableoid, t1_1.ctid
Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_1.a) AND
leakproof(t1_1.a))
+ Filter: (EXISTS (SubPlan 1) AND snoop(t1_1.a) AND
leakproof(t1_1.a))
SubPlan 1
-> Append
-> Seq Scan on public.t12 t12_1
@@ -2397,15 +2397,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND
a = 8;
-> Index Scan using t11_a_idx on public.t11 t1_2
Output: t1_2.a, t1_2.tableoid, t1_2.ctid
Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_2.a) AND
leakproof(t1_2.a))
+ Filter: (EXISTS (SubPlan 1) AND snoop(t1_2.a) AND
leakproof(t1_2.a))
-> Index Scan using t12_a_idx on public.t12 t1_3
Output: t1_3.a, t1_3.tableoid, t1_3.ctid
Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_3.a) AND
leakproof(t1_3.a))
+ Filter: (EXISTS (SubPlan 1) AND snoop(t1_3.a) AND
leakproof(t1_3.a))
-> Index Scan using t111_a_idx on public.t111 t1_4
Output: t1_4.a, t1_4.tableoid, t1_4.ctid
Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
- Filter: ((SubPlan 1) AND snoop(t1_4.a) AND
leakproof(t1_4.a))
+ Filter: (EXISTS (SubPlan 1) AND snoop(t1_4.a) AND
leakproof(t1_4.a))
(30 rows)
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
--
2.25.1