mirror of
https://github.com/postgres/postgres.git
synced 2026-07-15 20:52:53 -04:00
postgres_fdw: don't push down non-relabeling ArrayCoerceExpr
Commit62c3b4cd9dtaught postgres_fdw to push down ArrayCoerceExpr, but foreign_expr_walker() only recursed into the input array expression and never examined elemexpr, the per-element conversion that gives the coercion its semantics. deparseArrayCoerceExpr() then shipped a bare "arg::resulttype" cast, or nothing at all for an implicit-format coercion, leaving the remote server to re-resolve the element conversion against its own catalogs and session state. This produced wrong results or remote errors whenever the element conversion was not a plain relabeling, and it was inconsistent with how postgres_fdw treats the equivalent scalar coercions. An ArrayCoerceExpr was shipped even when its elemexpr was a cast function (whose shippability was never checked), a CoerceViaIO (e.g. float8out or byteaout, which depend on extra_float_digits / bytea_output that postgres_fdw sets differently on the remote session), or a CoerceToDomain (which pushes domain enforcement to the remote catalog). By contrast, a scalar CoerceViaIO is never shipped, and a scalar cast function is shipped only when it is shippable. Restrict pushdown to element coercions that are a plain relabeling, that is, elemexpr is a RelabelType or a bare CaseTestExpr. Any other element coercion is now evaluated locally. This keeps the common binary-coercible case pushed down, including "col = ANY($1)" with a varchar[]-to-text[] relabeling, which is the case62c3b4cd9dset out to optimize. Pushing down shippable element cast functions, to reach parity with the scalar case, is left out here for simplicity. Reported-by: Noah Misch <noah@leadboat.com> Discussion: https://postgr.es/m/20260711024234.43.noahmisch%40microsoft.com Backpatch-through: 19
This commit is contained in:
parent
7873db5369
commit
2349b106b6
3 changed files with 130 additions and 0 deletions
|
|
@ -707,6 +707,26 @@ foreign_expr_walker(Node *node,
|
|||
{
|
||||
ArrayCoerceExpr *e = (ArrayCoerceExpr *) node;
|
||||
|
||||
/*
|
||||
* Push down only when the per-element coercion is a plain
|
||||
* relabeling, that is, elemexpr is a RelabelType or a bare
|
||||
* CaseTestExpr. Any other element coercion -- a cast
|
||||
* function, an I/O conversion (CoerceViaIO), or a domain
|
||||
* coercion -- is kept local. We ship only a bare
|
||||
* "arg::resulttype" cast (nothing at all for an
|
||||
* implicit-format coercion), so a non-relabeling conversion
|
||||
* would be re-resolved against the remote server's catalogs
|
||||
* and session state and could silently change the result.
|
||||
* This matches the handling of the scalar coercions for the
|
||||
* I/O and domain cases (never shipped); an element cast
|
||||
* function is kept local too, which is more conservative than
|
||||
* the scalar case (a scalar cast function is shipped when it
|
||||
* is shippable).
|
||||
*/
|
||||
if (!IsA(e->elemexpr, RelabelType) &&
|
||||
!IsA(e->elemexpr, CaseTestExpr))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Recurse to input subexpression.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1219,6 +1219,81 @@ EXECUTE s(ARRAY['1','2']);
|
|||
|
||||
DEALLOCATE s;
|
||||
RESET plan_cache_mode;
|
||||
-- An ArrayCoerceExpr is pushed down only when its per-element coercion is
|
||||
-- a plain relabeling. An element cast function, an I/O conversion, or a
|
||||
-- domain coercion is instead evaluated locally.
|
||||
CREATE TABLE loct_acx (id int, ta text[], f8 float8[], txt text[], t text, ia int[], vc varchar[]);
|
||||
INSERT INTO loct_acx VALUES (1, '{12345}', '{0.30000000000000004}', '{0.3}', '5', '{5}', '{5}');
|
||||
CREATE FOREIGN TABLE ft_acx (id int, ta text[], f8 float8[], txt text[], t text, ia int[], vc varchar[])
|
||||
SERVER loopback OPTIONS (table_name 'loct_acx');
|
||||
-- element coercion via a cast function: not shippable, stays local
|
||||
CREATE FUNCTION acx_text2int(text) RETURNS int
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT AS 'BEGIN RETURN length($1); END';
|
||||
CREATE CAST (text AS integer) WITH FUNCTION acx_text2int(text);
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE ta::int[] = ARRAY[5];
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------
|
||||
Foreign Scan on public.ft_acx
|
||||
Output: id
|
||||
Filter: ((ft_acx.ta)::integer[] = '{5}'::integer[])
|
||||
Remote SQL: SELECT id, ta FROM public.loct_acx
|
||||
(4 rows)
|
||||
|
||||
DROP CAST (text AS integer);
|
||||
DROP FUNCTION acx_text2int(text);
|
||||
-- implicit-format element coercion (a cast function): stays local, and the
|
||||
-- coercion is not silently dropped from an otherwise pushed-down qual
|
||||
CREATE CAST (integer AS text) WITH FUNCTION pg_catalog.to_hex(integer) AS IMPLICIT;
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE t = ANY (ia);
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------
|
||||
Foreign Scan on public.ft_acx
|
||||
Output: id
|
||||
Filter: (ft_acx.t = ANY ((ft_acx.ia)::text[]))
|
||||
Remote SQL: SELECT id, t, ia FROM public.loct_acx
|
||||
(4 rows)
|
||||
|
||||
DROP CAST (integer AS text);
|
||||
-- element coercion via a GUC-sensitive I/O conversion: stays local, so the
|
||||
-- result matches local evaluation despite the forced remote extra_float_digits
|
||||
SET extra_float_digits = 0;
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE f8::text[] = txt;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------
|
||||
Foreign Scan on public.ft_acx
|
||||
Output: id
|
||||
Filter: ((ft_acx.f8)::text[] = ft_acx.txt)
|
||||
Remote SQL: SELECT id, f8, txt FROM public.loct_acx
|
||||
(4 rows)
|
||||
|
||||
SELECT id FROM ft_acx WHERE f8::text[] = txt;
|
||||
id
|
||||
----
|
||||
1
|
||||
(1 row)
|
||||
|
||||
RESET extra_float_digits;
|
||||
-- a plain relabeling element coercion (varchar[] to text[]) is still pushed down
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE t = ANY (vc);
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------
|
||||
Foreign Scan on public.ft_acx
|
||||
Output: id
|
||||
Remote SQL: SELECT id FROM public.loct_acx WHERE ((t = ANY (vc)))
|
||||
(3 rows)
|
||||
|
||||
SELECT id FROM ft_acx WHERE t = ANY (vc);
|
||||
id
|
||||
----
|
||||
1
|
||||
(1 row)
|
||||
|
||||
DROP FOREIGN TABLE ft_acx;
|
||||
DROP TABLE loct_acx;
|
||||
-- a regconfig constant referring to this text search configuration
|
||||
-- is initially unshippable
|
||||
CREATE TEXT SEARCH CONFIGURATION public.custom_search
|
||||
|
|
|
|||
|
|
@ -485,6 +485,41 @@ EXECUTE s(ARRAY['1','2']);
|
|||
DEALLOCATE s;
|
||||
RESET plan_cache_mode;
|
||||
|
||||
-- An ArrayCoerceExpr is pushed down only when its per-element coercion is
|
||||
-- a plain relabeling. An element cast function, an I/O conversion, or a
|
||||
-- domain coercion is instead evaluated locally.
|
||||
CREATE TABLE loct_acx (id int, ta text[], f8 float8[], txt text[], t text, ia int[], vc varchar[]);
|
||||
INSERT INTO loct_acx VALUES (1, '{12345}', '{0.30000000000000004}', '{0.3}', '5', '{5}', '{5}');
|
||||
CREATE FOREIGN TABLE ft_acx (id int, ta text[], f8 float8[], txt text[], t text, ia int[], vc varchar[])
|
||||
SERVER loopback OPTIONS (table_name 'loct_acx');
|
||||
-- element coercion via a cast function: not shippable, stays local
|
||||
CREATE FUNCTION acx_text2int(text) RETURNS int
|
||||
LANGUAGE plpgsql IMMUTABLE STRICT AS 'BEGIN RETURN length($1); END';
|
||||
CREATE CAST (text AS integer) WITH FUNCTION acx_text2int(text);
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE ta::int[] = ARRAY[5];
|
||||
DROP CAST (text AS integer);
|
||||
DROP FUNCTION acx_text2int(text);
|
||||
-- implicit-format element coercion (a cast function): stays local, and the
|
||||
-- coercion is not silently dropped from an otherwise pushed-down qual
|
||||
CREATE CAST (integer AS text) WITH FUNCTION pg_catalog.to_hex(integer) AS IMPLICIT;
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE t = ANY (ia);
|
||||
DROP CAST (integer AS text);
|
||||
-- element coercion via a GUC-sensitive I/O conversion: stays local, so the
|
||||
-- result matches local evaluation despite the forced remote extra_float_digits
|
||||
SET extra_float_digits = 0;
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE f8::text[] = txt;
|
||||
SELECT id FROM ft_acx WHERE f8::text[] = txt;
|
||||
RESET extra_float_digits;
|
||||
-- a plain relabeling element coercion (varchar[] to text[]) is still pushed down
|
||||
EXPLAIN (VERBOSE, COSTS OFF)
|
||||
SELECT id FROM ft_acx WHERE t = ANY (vc);
|
||||
SELECT id FROM ft_acx WHERE t = ANY (vc);
|
||||
DROP FOREIGN TABLE ft_acx;
|
||||
DROP TABLE loct_acx;
|
||||
|
||||
-- a regconfig constant referring to this text search configuration
|
||||
-- is initially unshippable
|
||||
CREATE TEXT SEARCH CONFIGURATION public.custom_search
|
||||
|
|
|
|||
Loading…
Reference in a new issue