LCOV - code coverage report
Current view: top level - src/test/modules/test_predtest - test_predtest.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 111 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 3 0
Legend: Lines:     hit not hit

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

Generated by: LCOV version 2.3.2-1