Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wP4EY-000Nyr-2j for pgsql-hackers@arkaria.postgresql.org; Mon, 18 May 2026 20:06:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wP4EV-0031u2-0a for pgsql-hackers@arkaria.postgresql.org; Mon, 18 May 2026 20:06:36 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wP4EU-0031tu-2P for pgsql-hackers@lists.postgresql.org; Mon, 18 May 2026 20:06:35 +0000 Received: from mail.postgrespro.ru ([93.174.132.70]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wP4ET-00000000F9E-0x80 for pgsql-hackers@lists.postgresql.org; Mon, 18 May 2026 20:06:35 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=postgrespro.ru; s=mx2023; t=1779134791; bh=oXj3mp9iImdrwEwdxT+vVcxb9hhYa5YB1PHLpq3V1tU=; h=Date:From:To:Cc:Subject:In-Reply-To:References:Message-ID:From; b=aECNWDSLKKRPgx+2fpXL7CNRTvy+qZkwJlGvnPWIjstJMm2prwrqkEpeE2yNmFWwm AujXp6ejwm1oKSjp4jWb6dBlQo8W4+hDKWw+kKP1+JVEb7mC9FATx7fDH1y9neF8bA Vs32SV4cniK4zZkEbj55Pah0xEO3ehd9eo4zipAFlaiFFX0Q3FViqFs/bQTTbI5nMa 3580vEaFm3oFRA37754k98dabkRhGsEfDwR+sdEp5TutsdtQGHwGOEavfvumw4dCaF n415P/SgFdrwTWiq3/Bo4wHHsZSxFXxN/g2wgswaxADFAD4005uWvfWWSTMwha1Nc3 0NClxaVYlHCag== Received: from mail.l.postgrespro.ru (webmail-slave-mstn.l.postgrespro.ru [192.168.2.28]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (Client did not present a certificate) (Authenticated sender: a.pyhalov@postgrespro.ru) by mail.postgrespro.ru (Postfix/587) with ESMTPSA id 4660B6009B; Mon, 18 May 2026 23:06:31 +0300 (MSK) MIME-Version: 1.0 Date: Mon, 18 May 2026 23:06:31 +0300 From: Alexander Pyhalov To: Alexander Korotkov Cc: solaimurugan vellaipandiyan , =?UTF-8?Q?=C3=81lvaro_Herrera?= , g.kashkin@postgrespro.ru, Ashutosh Bapat , PostgreSQL Hackers Subject: Re: Function scan FDW pushdown In-Reply-To: References: <202508051955.sao7ucalcce7@alvherre.pgsql> <7e2abeb3ea9bc7ca024f4e457dce33f5@postgrespro.ru> Message-ID: X-Sender: a.pyhalov@postgrespro.ru Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-KSMG-AntiPhishing: NotDetected X-KSMG-AntiSpam-Interceptor-Info: not scanned X-KSMG-AntiSpam-Status: not scanned, disabled by settings X-KSMG-AntiVirus: Kaspersky Secure Mail Gateway, version 3.0.0.9059, bases: 2026/05/18 18:16:00 #28210026 X-KSMG-AntiVirus-Status: NotDetected, skipped X-KSMG-LinksScanning: not scanned, disabled by settings X-KSMG-Message-Action: skipped X-KSMG-Rule-ID: 1 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Alexander Korotkov писал(а) 2026-05-18 13:34: > Hi, Alexander! > > The revised patch is attached. > > On Tue, May 12, 2026 at 11:09 AM Alexander Pyhalov > wrote: >> 1) deparseColumnRef() doesn't account for whole row vars. >> In queries like >> >> UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))']) >> AS >> t (bx) WHERE r.a = area(t.bx) >> >> it fails with assert that varattno should be > 0. When we lock >> non-relation RTE, we select whole row var, and we have to deparse it >> for >> function RTE. >> >> You've removed check for function return type. This seems to be >> dangerous. Old example >> >> CREATE OR REPLACE FUNCTION f_ret_record() RETURNS record AS $$ SELECT >> (1,2)::record $$ language SQL IMMUTABLE; >> ALTER EXTENSION postgres_fdw ADD function f_ret_record(); >> EXPLAIN (VERBOSE, COSTS OFF) >> SELECT s FROM remote_tbl rt, f_ret_record() AS s(a int, b int) >> WHERE s.a = rt.a; >> >> fails with >> >> ERROR: a column definition list is required for functions returning >> "record" > > function_rte_pushdown_ok() now calls get_expr_result_type() and > rejects anything that isn't TYPEFUNC_SCALAR (also RECORDOID/VOIDOID), > so f_ret_record() no longer reaches the remote side. > deparseColumnRef() now handles varattno == 0 for RTE_FUNCTION and > emits ROW(f.c1, ..., f.c) from rte->eref->colnames. > >> 2) postgresBeginForeignScan() can step on function RTE, and doesn't >> know >> what to do with it: >> SELECT * FROM unnest(array[2,3,4]) n, remote_tbl r WHERE r.a = n; >> ERROR: cache lookup failed for foreign table 0 >> >> So, we need to look for the first RTE_RELATION, as in older patch >> version. > > The scanrelid == 0 branch in postgresBeginForeignScan() now scans > fs_base_relids until it finds an RTE_RELATION. An explicit > elog(ERROR) guards the (theoretically impossible) case where no > foreign RTE is found. > >> 3) A lot of complexity in the old patch version was in making it >> possible to find out RTE_FUNCTION attribute types after planing, as >> it's >> necessary to correctly handle joins. In this version >> get_tupdesc_for_join_scan_tuples() doesn't handle function RTEs. This >> means, when we try to find out type for attribute types for joins, >> we'll >> get errors. This can be seen in queries like >> >> UPDATE remote_tbl r SET b=CASE WHEN random()>=0 THEN 5 ELSE 0 END FROM >> UNNEST(array[box '((2,3),(-2,-3))']) AS t (bx) WHERE r.a = area(t.bx) >> RETURNING a,b; >> >> Now it fails on earlier stages (with "column f2.c0 does not exist"), >> but >> if we fix it, we'll get something like >> "ERROR: input of anonymous composite types is not implemented" >> >> Overall, function_rte_pushdown_ok() now allows more strange >> constructions. Could it skip Vars from outside of joinrel->relids? Can >> we safely ship function with parameters in arguments? I'm not sure. > > Restored the per-function metadata you had in v2/v3. > FdwScanPrivateFunctions (list of (funcid, funcrettype, funccollation) > indexed by RTI-offset) and FdwScanPrivateMinRTIndex are now saved in > fdw_private by postgresGetForeignPlan(). > get_tupdesc_for_join_scan_tuples() now has an RTE_FUNCTION branch that > rebuilds the tuple descriptor from this metadata, exactly as in your > patch. Hi. I am a bit confused about this comment (and code): /* * DirectModify on a foreign join: pass NIL/0 for the function * metadata. We don't currently push function RTEs through the * direct-modify path, so there are no whole-row Vars pointing at * function-RTE tuples to reconstruct. */ tupdesc = get_tupdesc_for_join_scan_tuples(node, NIL, 0); We evidently go through this code path when executing example UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))']) AS t (bx) WHERE r.a = area(t.bx) RETURNING a,b; But don't need whole row var in returning list.... However, we still can step on this issue. UPDATE remote_tbl r SET b=5 FROM UNNEST(array[box '((2,3),(-2,-3))'], array[int '1']) AS t (bx, i) WHERE r.a = area(t.bx) RETURNING a,b,t; ERROR: input of anonymous composite types is not implemented CONTEXT: whole-row reference to foreign table "t" -- Best regards, Alexander Pyhalov, Postgres Professional