Thread: Unexpected plan width with foreign data wrapper

Unexpected plan width with foreign data wrapper

From
Dmitry Bogatov
Date:
Hello.

I work on fixing test suite (make installcheck) failures in Multicorn
project[1]. The purpose of project is to make it possible to write
Foreign Data Wrappers[2] in Python, not in C[3].

Test suite executes SQL statements with psql and compares output with
expected. If they are different, test is considered failed.

Major source of test failures is width estimation in query plan
explaination. For example, for following statements:

    CREATE EXTENSION multicorn;
    CREATE server multicorn_srv foreign data wrapper multicorn options (
        wrapper 'multicorn.testfdw.TestForeignDataWrapper'
    );
    CREATE user mapping for postgres server multicorn_srv options (usermapping 'test');

    CREATE foreign table testmulticorn (
        test1 date,
        test2 timestamp
    ) server multicorn_srv options (
        option1 'option1',
        test_type 'date'
    );

    -- Test sort pushdown asked
    EXPLAIN SELECT * FROM testmulticorn ORDER BY test1 DESC;

I have mismatch, described by this `diff expected actual`:

    - Foreign Scan on testmulticorn  (cost=10.00..400.00 rows=20 width=20)
    + Foreign Scan on testmulticorn  (cost=10.00..400.00 rows=20 width=12)

Width 20 is understandable: this is value assigned to
`baserel->reltarget->width` in GetForeignRelSize callback function.
Origin of value `12' is unknown to me. I have some observations on how
width estimate propagates through callback functions, though.

Behaviour differs on different versions of postgresql:

1. Postgresql-11

 * Value assigned to `baserel->reltarget->width` in GetForeignRelSize
   callback stays same in GetForeignPaths, but in GetForeignPlan it is
   changed. In this test, from 20 to 12.

 * If I assign it in GetForeignPlan function back to value 20 (value can be
   transfered via `baserel->fdw_private` field), expected value 20 will
   appear in output of `psql`. Test fixed, but with no understanding
   why.

2. Postgresql-9.6

 * Value assigned to `baserel->reltarget->width` in GetForeignRelSize
   stays the same in GetForeignPaths and GetForeignPlan, but `psql`
   still outputs another value. Test failed.

From what I can grep, psql output is generated in
`src/backend/commands/explain.c:1206`:

    appendStringInfo(es->str, "  (cost=%.2f..%.2f rows=%.0f width=%d)",
                     plan->startup_cost, plan->total_cost,
                     plan->plan_rows, plan->plan_width);

But I do not understand, where these values flow from.

Questions are:

 * Why do postgresql overrides width estimate, provided by foreign data
   wrapper?
 * How to fix tests in presence of such behaviour?

By the way, documentation in [2] is outdated: it refers to
`baserel->width`, which is replaced by `baserel->reltarget->width` in
commit 19a5411, and not present in 9.6.

 [1] https://github.com/Kozea/Multicorn
 [2] https://www.postgresql.org/docs/11/fdw-callbacks.html
 [3] https://multicorn.org