mirror of
https://github.com/postgres/postgres.git
synced 2025-11-30 00:03:16 -05:00
Create a new header optimizer/optimizer.h, which exposes just the planner functions that can be used "at arm's length", without need to access Paths or the other planner-internal data structures defined in nodes/relation.h. This is intended to provide the whole planner API seen by most of the rest of the system; although FDWs still need to use additional stuff, and more thought is also needed about just what selfuncs.c should rely on. The main point of doing this now is to limit the amount of new #include baggage that will be needed by "planner support functions", which I expect to introduce later, and which will be in relevant datatype modules rather than anywhere near the planner. This commit just moves relevant declarations into optimizer.h from other header files (a couple of which go away because everything got moved), and adjusts #include lists to match. There's further cleanup that could be done if we want to decide that some stuff being exposed by optimizer.h doesn't belong in the planner at all, but I'll leave that for another day. Discussion: https://postgr.es/m/11460.1548706639@sss.pgh.pa.us
219 lines
6.6 KiB
C
219 lines
6.6 KiB
C
/*--------------------------------------------------------------------------
|
|
*
|
|
* test_predtest.c
|
|
* Test correctness of optimizer's predicate proof logic.
|
|
*
|
|
* Copyright (c) 2018-2019, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/test/modules/test_predtest/test_predtest.c
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/htup_details.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "executor/spi.h"
|
|
#include "funcapi.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "utils/builtins.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
/*
|
|
* test_predtest(query text) returns record
|
|
*/
|
|
PG_FUNCTION_INFO_V1(test_predtest);
|
|
|
|
Datum
|
|
test_predtest(PG_FUNCTION_ARGS)
|
|
{
|
|
text *txt = PG_GETARG_TEXT_PP(0);
|
|
char *query_string = text_to_cstring(txt);
|
|
SPIPlanPtr spiplan;
|
|
int spirc;
|
|
TupleDesc tupdesc;
|
|
bool s_i_holds,
|
|
w_i_holds,
|
|
s_r_holds,
|
|
w_r_holds;
|
|
CachedPlan *cplan;
|
|
PlannedStmt *stmt;
|
|
Plan *plan;
|
|
Expr *clause1;
|
|
Expr *clause2;
|
|
bool strong_implied_by,
|
|
weak_implied_by,
|
|
strong_refuted_by,
|
|
weak_refuted_by;
|
|
Datum values[8];
|
|
bool nulls[8];
|
|
int i;
|
|
|
|
/* We use SPI to parse, plan, and execute the test query */
|
|
if (SPI_connect() != SPI_OK_CONNECT)
|
|
elog(ERROR, "SPI_connect failed");
|
|
|
|
/*
|
|
* First, plan and execute the query, and inspect the results. To the
|
|
* extent that the query fully exercises the two expressions, this
|
|
* provides an experimental indication of whether implication or
|
|
* refutation holds.
|
|
*/
|
|
spiplan = SPI_prepare(query_string, 0, NULL);
|
|
if (spiplan == NULL)
|
|
elog(ERROR, "SPI_prepare failed for \"%s\"", query_string);
|
|
|
|
spirc = SPI_execute_plan(spiplan, NULL, NULL, true, 0);
|
|
if (spirc != SPI_OK_SELECT)
|
|
elog(ERROR, "failed to execute \"%s\"", query_string);
|
|
tupdesc = SPI_tuptable->tupdesc;
|
|
if (tupdesc->natts != 2 ||
|
|
TupleDescAttr(tupdesc, 0)->atttypid != BOOLOID ||
|
|
TupleDescAttr(tupdesc, 1)->atttypid != BOOLOID)
|
|
elog(ERROR, "query must yield two boolean columns");
|
|
|
|
s_i_holds = w_i_holds = s_r_holds = w_r_holds = true;
|
|
for (i = 0; i < SPI_processed; i++)
|
|
{
|
|
HeapTuple tup = SPI_tuptable->vals[i];
|
|
Datum dat;
|
|
bool isnull;
|
|
char c1,
|
|
c2;
|
|
|
|
/* Extract column values in a 3-way representation */
|
|
dat = SPI_getbinval(tup, tupdesc, 1, &isnull);
|
|
if (isnull)
|
|
c1 = 'n';
|
|
else if (DatumGetBool(dat))
|
|
c1 = 't';
|
|
else
|
|
c1 = 'f';
|
|
|
|
dat = SPI_getbinval(tup, tupdesc, 2, &isnull);
|
|
if (isnull)
|
|
c2 = 'n';
|
|
else if (DatumGetBool(dat))
|
|
c2 = 't';
|
|
else
|
|
c2 = 'f';
|
|
|
|
/* Check for violations of various proof conditions */
|
|
|
|
/* strong implication: truth of c2 implies truth of c1 */
|
|
if (c2 == 't' && c1 != 't')
|
|
s_i_holds = false;
|
|
/* weak implication: non-falsity of c2 implies non-falsity of c1 */
|
|
if (c2 != 'f' && c1 == 'f')
|
|
w_i_holds = false;
|
|
/* strong refutation: truth of c2 implies falsity of c1 */
|
|
if (c2 == 't' && c1 != 'f')
|
|
s_r_holds = false;
|
|
/* weak refutation: truth of c2 implies non-truth of c1 */
|
|
if (c2 == 't' && c1 == 't')
|
|
w_r_holds = false;
|
|
}
|
|
|
|
/*
|
|
* Now, dig the clause querytrees out of the plan, and see what predtest.c
|
|
* does with them.
|
|
*/
|
|
cplan = SPI_plan_get_cached_plan(spiplan);
|
|
|
|
if (list_length(cplan->stmt_list) != 1)
|
|
elog(ERROR, "failed to decipher query plan");
|
|
stmt = linitial_node(PlannedStmt, cplan->stmt_list);
|
|
if (stmt->commandType != CMD_SELECT)
|
|
elog(ERROR, "failed to decipher query plan");
|
|
plan = stmt->planTree;
|
|
Assert(list_length(plan->targetlist) >= 2);
|
|
clause1 = castNode(TargetEntry, linitial(plan->targetlist))->expr;
|
|
clause2 = castNode(TargetEntry, lsecond(plan->targetlist))->expr;
|
|
|
|
/*
|
|
* Because the clauses are in the SELECT list, preprocess_expression did
|
|
* not pass them through canonicalize_qual nor make_ands_implicit.
|
|
*
|
|
* We can't do canonicalize_qual here, since it's unclear whether the
|
|
* expressions ought to be treated as WHERE or CHECK clauses. Fortunately,
|
|
* useful test expressions wouldn't be affected by those transformations
|
|
* anyway. We should do make_ands_implicit, though.
|
|
*
|
|
* Another way in which this does not exactly duplicate the normal usage
|
|
* of the proof functions is that they are often given qual clauses
|
|
* containing RestrictInfo nodes. But since predtest.c just looks through
|
|
* those anyway, it seems OK to not worry about that point.
|
|
*/
|
|
clause1 = (Expr *) make_ands_implicit(clause1);
|
|
clause2 = (Expr *) make_ands_implicit(clause2);
|
|
|
|
strong_implied_by = predicate_implied_by((List *) clause1,
|
|
(List *) clause2,
|
|
false);
|
|
|
|
weak_implied_by = predicate_implied_by((List *) clause1,
|
|
(List *) clause2,
|
|
true);
|
|
|
|
strong_refuted_by = predicate_refuted_by((List *) clause1,
|
|
(List *) clause2,
|
|
false);
|
|
|
|
weak_refuted_by = predicate_refuted_by((List *) clause1,
|
|
(List *) clause2,
|
|
true);
|
|
|
|
/*
|
|
* Issue warning if any proof is demonstrably incorrect.
|
|
*/
|
|
if (strong_implied_by && !s_i_holds)
|
|
elog(WARNING, "strong_implied_by result is incorrect");
|
|
if (weak_implied_by && !w_i_holds)
|
|
elog(WARNING, "weak_implied_by result is incorrect");
|
|
if (strong_refuted_by && !s_r_holds)
|
|
elog(WARNING, "strong_refuted_by result is incorrect");
|
|
if (weak_refuted_by && !w_r_holds)
|
|
elog(WARNING, "weak_refuted_by result is incorrect");
|
|
|
|
/*
|
|
* Clean up and return a record of the results.
|
|
*/
|
|
if (SPI_finish() != SPI_OK_FINISH)
|
|
elog(ERROR, "SPI_finish failed");
|
|
|
|
tupdesc = CreateTemplateTupleDesc(8);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1,
|
|
"strong_implied_by", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2,
|
|
"weak_implied_by", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3,
|
|
"strong_refuted_by", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4,
|
|
"weak_refuted_by", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5,
|
|
"s_i_holds", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 6,
|
|
"w_i_holds", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 7,
|
|
"s_r_holds", BOOLOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 8,
|
|
"w_r_holds", BOOLOID, -1, 0);
|
|
tupdesc = BlessTupleDesc(tupdesc);
|
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
|
values[0] = BoolGetDatum(strong_implied_by);
|
|
values[1] = BoolGetDatum(weak_implied_by);
|
|
values[2] = BoolGetDatum(strong_refuted_by);
|
|
values[3] = BoolGetDatum(weak_refuted_by);
|
|
values[4] = BoolGetDatum(s_i_holds);
|
|
values[5] = BoolGetDatum(w_i_holds);
|
|
values[6] = BoolGetDatum(s_r_holds);
|
|
values[7] = BoolGetDatum(w_r_holds);
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
|
|
}
|