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