So it turns out there's another case we have to check for, as reported on IRC by Yaroslav Schekin (though I've simplified his query a little):
WITH RECURSIVE x(a) AS ((VALUES ('a'),('b')) UNION ALL (WITH z AS /*NOT*/ MATERIALIZED (SELECT a FROM x) SELECT z.a || z1.a AS a FROM z CROSS JOIN z AS z1 WHERE length(z.a || z1.a) < 5)) SELECT * FROM x; Here, uncommenting that NOT actually changes the result, from 22 rows to 4 rows, because we end up generating multiple worktable scans and the recursion logic is not set up to handle that. So what I think we need to do here is to forbid inlining if (a) the refcount is greater than 1 and (b) the CTE in question contains, recursively anywhere inside its rtable or the rtables of any of its nested CTEs, a "self_reference" RTE. We only need to make this check if we're somewhere (possibly nested) inside a recursive query, but I don't know how practical it will be to test that condition at the point we do inlining; doing it unconditionally will (I think) be harmless because self_reference will not be set. -- Andrew (irc:RhodiumToad)