]> granicus.if.org Git - postgresql/blob - src/test/modules/test_predtest/test_predtest.c
Make some small planner API cleanups.
[postgresql] / src / test / modules / test_predtest / test_predtest.c
1 /*--------------------------------------------------------------------------
2  *
3  * test_predtest.c
4  *              Test correctness of optimizer's predicate proof logic.
5  *
6  * Copyright (c) 2018-2019, PostgreSQL Global Development Group
7  *
8  * IDENTIFICATION
9  *              src/test/modules/test_predtest/test_predtest.c
10  *
11  * -------------------------------------------------------------------------
12  */
13
14 #include "postgres.h"
15
16 #include "access/htup_details.h"
17 #include "catalog/pg_type.h"
18 #include "executor/spi.h"
19 #include "funcapi.h"
20 #include "nodes/makefuncs.h"
21 #include "optimizer/clauses.h"
22 #include "optimizer/predtest.h"
23 #include "utils/builtins.h"
24
25 PG_MODULE_MAGIC;
26
27 /*
28  * test_predtest(query text) returns record
29  */
30 PG_FUNCTION_INFO_V1(test_predtest);
31
32 Datum
33 test_predtest(PG_FUNCTION_ARGS)
34 {
35         text       *txt = PG_GETARG_TEXT_PP(0);
36         char       *query_string = text_to_cstring(txt);
37         SPIPlanPtr      spiplan;
38         int                     spirc;
39         TupleDesc       tupdesc;
40         bool            s_i_holds,
41                                 w_i_holds,
42                                 s_r_holds,
43                                 w_r_holds;
44         CachedPlan *cplan;
45         PlannedStmt *stmt;
46         Plan       *plan;
47         Expr       *clause1;
48         Expr       *clause2;
49         bool            strong_implied_by,
50                                 weak_implied_by,
51                                 strong_refuted_by,
52                                 weak_refuted_by;
53         Datum           values[8];
54         bool            nulls[8];
55         int                     i;
56
57         /* We use SPI to parse, plan, and execute the test query */
58         if (SPI_connect() != SPI_OK_CONNECT)
59                 elog(ERROR, "SPI_connect failed");
60
61         /*
62          * First, plan and execute the query, and inspect the results.  To the
63          * extent that the query fully exercises the two expressions, this
64          * provides an experimental indication of whether implication or
65          * refutation holds.
66          */
67         spiplan = SPI_prepare(query_string, 0, NULL);
68         if (spiplan == NULL)
69                 elog(ERROR, "SPI_prepare failed for \"%s\"", query_string);
70
71         spirc = SPI_execute_plan(spiplan, NULL, NULL, true, 0);
72         if (spirc != SPI_OK_SELECT)
73                 elog(ERROR, "failed to execute \"%s\"", query_string);
74         tupdesc = SPI_tuptable->tupdesc;
75         if (tupdesc->natts != 2 ||
76                 TupleDescAttr(tupdesc, 0)->atttypid != BOOLOID ||
77                 TupleDescAttr(tupdesc, 1)->atttypid != BOOLOID)
78                 elog(ERROR, "query must yield two boolean columns");
79
80         s_i_holds = w_i_holds = s_r_holds = w_r_holds = true;
81         for (i = 0; i < SPI_processed; i++)
82         {
83                 HeapTuple       tup = SPI_tuptable->vals[i];
84                 Datum           dat;
85                 bool            isnull;
86                 char            c1,
87                                         c2;
88
89                 /* Extract column values in a 3-way representation */
90                 dat = SPI_getbinval(tup, tupdesc, 1, &isnull);
91                 if (isnull)
92                         c1 = 'n';
93                 else if (DatumGetBool(dat))
94                         c1 = 't';
95                 else
96                         c1 = 'f';
97
98                 dat = SPI_getbinval(tup, tupdesc, 2, &isnull);
99                 if (isnull)
100                         c2 = 'n';
101                 else if (DatumGetBool(dat))
102                         c2 = 't';
103                 else
104                         c2 = 'f';
105
106                 /* Check for violations of various proof conditions */
107
108                 /* strong implication: truth of c2 implies truth of c1 */
109                 if (c2 == 't' && c1 != 't')
110                         s_i_holds = false;
111                 /* weak implication: non-falsity of c2 implies non-falsity of c1 */
112                 if (c2 != 'f' && c1 == 'f')
113                         w_i_holds = false;
114                 /* strong refutation: truth of c2 implies falsity of c1 */
115                 if (c2 == 't' && c1 != 'f')
116                         s_r_holds = false;
117                 /* weak refutation: truth of c2 implies non-truth of c1 */
118                 if (c2 == 't' && c1 == 't')
119                         w_r_holds = false;
120         }
121
122         /*
123          * Now, dig the clause querytrees out of the plan, and see what predtest.c
124          * does with them.
125          */
126         cplan = SPI_plan_get_cached_plan(spiplan);
127
128         if (list_length(cplan->stmt_list) != 1)
129                 elog(ERROR, "failed to decipher query plan");
130         stmt = linitial_node(PlannedStmt, cplan->stmt_list);
131         if (stmt->commandType != CMD_SELECT)
132                 elog(ERROR, "failed to decipher query plan");
133         plan = stmt->planTree;
134         Assert(list_length(plan->targetlist) >= 2);
135         clause1 = castNode(TargetEntry, linitial(plan->targetlist))->expr;
136         clause2 = castNode(TargetEntry, lsecond(plan->targetlist))->expr;
137
138         /*
139          * Because the clauses are in the SELECT list, preprocess_expression did
140          * not pass them through canonicalize_qual nor make_ands_implicit.
141          *
142          * We can't do canonicalize_qual here, since it's unclear whether the
143          * expressions ought to be treated as WHERE or CHECK clauses. Fortunately,
144          * useful test expressions wouldn't be affected by those transformations
145          * anyway.  We should do make_ands_implicit, though.
146          *
147          * Another way in which this does not exactly duplicate the normal usage
148          * of the proof functions is that they are often given qual clauses
149          * containing RestrictInfo nodes.  But since predtest.c just looks through
150          * those anyway, it seems OK to not worry about that point.
151          */
152         clause1 = (Expr *) make_ands_implicit(clause1);
153         clause2 = (Expr *) make_ands_implicit(clause2);
154
155         strong_implied_by = predicate_implied_by((List *) clause1,
156                                                                                          (List *) clause2,
157                                                                                          false);
158
159         weak_implied_by = predicate_implied_by((List *) clause1,
160                                                                                    (List *) clause2,
161                                                                                    true);
162
163         strong_refuted_by = predicate_refuted_by((List *) clause1,
164                                                                                          (List *) clause2,
165                                                                                          false);
166
167         weak_refuted_by = predicate_refuted_by((List *) clause1,
168                                                                                    (List *) clause2,
169                                                                                    true);
170
171         /*
172          * Issue warning if any proof is demonstrably incorrect.
173          */
174         if (strong_implied_by && !s_i_holds)
175                 elog(WARNING, "strong_implied_by result is incorrect");
176         if (weak_implied_by && !w_i_holds)
177                 elog(WARNING, "weak_implied_by result is incorrect");
178         if (strong_refuted_by && !s_r_holds)
179                 elog(WARNING, "strong_refuted_by result is incorrect");
180         if (weak_refuted_by && !w_r_holds)
181                 elog(WARNING, "weak_refuted_by result is incorrect");
182
183         /*
184          * Clean up and return a record of the results.
185          */
186         if (SPI_finish() != SPI_OK_FINISH)
187                 elog(ERROR, "SPI_finish failed");
188
189         tupdesc = CreateTemplateTupleDesc(8);
190         TupleDescInitEntry(tupdesc, (AttrNumber) 1,
191                                            "strong_implied_by", BOOLOID, -1, 0);
192         TupleDescInitEntry(tupdesc, (AttrNumber) 2,
193                                            "weak_implied_by", BOOLOID, -1, 0);
194         TupleDescInitEntry(tupdesc, (AttrNumber) 3,
195                                            "strong_refuted_by", BOOLOID, -1, 0);
196         TupleDescInitEntry(tupdesc, (AttrNumber) 4,
197                                            "weak_refuted_by", BOOLOID, -1, 0);
198         TupleDescInitEntry(tupdesc, (AttrNumber) 5,
199                                            "s_i_holds", BOOLOID, -1, 0);
200         TupleDescInitEntry(tupdesc, (AttrNumber) 6,
201                                            "w_i_holds", BOOLOID, -1, 0);
202         TupleDescInitEntry(tupdesc, (AttrNumber) 7,
203                                            "s_r_holds", BOOLOID, -1, 0);
204         TupleDescInitEntry(tupdesc, (AttrNumber) 8,
205                                            "w_r_holds", BOOLOID, -1, 0);
206         tupdesc = BlessTupleDesc(tupdesc);
207
208         MemSet(nulls, 0, sizeof(nulls));
209         values[0] = BoolGetDatum(strong_implied_by);
210         values[1] = BoolGetDatum(weak_implied_by);
211         values[2] = BoolGetDatum(strong_refuted_by);
212         values[3] = BoolGetDatum(weak_refuted_by);
213         values[4] = BoolGetDatum(s_i_holds);
214         values[5] = BoolGetDatum(w_i_holds);
215         values[6] = BoolGetDatum(s_r_holds);
216         values[7] = BoolGetDatum(w_r_holds);
217
218         PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
219 }