导入包含索引中使用的函数的 SQL 转储时出错
Errors importing SQL dump containing functions used in indexes
在 PostgreSQL 11 上,我定期转储备份快照,有时将它们导入具有相同设置的开发系统。没什么特别的:
# Dump
ps_dump -OU <user> <database> >dump.sql
# Restore
psql -U <user> -f dump.sql <database>
但是,转储中的两个索引在恢复时抛出错误。我将它归结为以下转储,并删除了所有不相关的内容:
--
-- PostgreSQL database dump
--
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: add_days(timestamp without time zone, integer, text); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.add_days(timestamp without time zone, integer, text DEFAULT 'Europe/Zurich'::text) RETURNS timestamp without time zone
LANGUAGE sql IMMUTABLE
SET search_path TO 'public', 'pg_temp'
AS $_$
SELECT ((::timestamp AT TIME ZONE 'UTC' AT TIME ZONE + INTERVAL '1 day' * ) AT TIME ZONE )::timestamp
$_$;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: projects; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.projects (
id integer NOT NULL,
started_at timestamp without time zone,
duration integer
);
--
-- Name: ended_at(public.projects); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
AS $_$
SELECT add_days(.started_at, .duration)
$_$;
--
-- Name: index_projects_on_ended_at; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_projects_on_ended_at ON public.projects USING btree (public.ended_at(projects.*));
还原此转储会产生以下错误:
psql:dumped.sql:60: ERROR: function add_days(timestamp without time zone, integer) does not exist
LINE 2: SELECT add_days(.started_at, .duration)
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY:
SELECT add_days(.started_at, .duration)
CONTEXT: SQL function "ended_at" during inlining
以下解决了问题:使用架构前缀 add_days
如下所示:
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
AS $_$
SELECT public.add_days(.started_at, .duration)
$_$;
案件结案?不完全的。我想了解这里的问题是什么。
我猜,由 pg_dump 生成的行 SELECT pg_catalog.set_config('search_path', '', false);
会阻止 add_days
被发现,除非带有明确的模式前缀。
但是,为什么以下替代方法(添加 search_path)不起作用?
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
SET search_path TO 'public', 'pg_temp'
AS $_$
SELECT add_days(.started_at, .duration)
$_$;
这会触发一个完全不同的错误:
psql:dumped.sql:58: ERROR: functions in index expression must be marked IMMUTABLE
好吧,现在我很困惑。有人能告诉我这是怎么回事吗?
这里有两个不同的问题。
为什么恢复转储时出现错误?
问题是由修复了 PostgreSQL 安全问题的 this 补丁引起的。
在此更改之前,pg_dump
/ pg_restore
会将 search_path
设置为如下所示:
SET search_path = dumped_schema, pg_catalog;
问题在于索引定义(和其他地方)中使用的任何函数或运算符将首先在 dumped_schema
中搜索。
恶意用户可以利用它在恢复期间以超级用户权限执行其功能。
你已经想到了解决问题的方法
这确实很烦人,但鉴于到目前为止您的功能受当前 search_path
设置的支配(任何人都可以使用简单的 SET
命令更改),我会明白它本身就是一种改进。
为什么对SET search_path
函数不起作用?
这里的问题是在索引中使用的任何函数都必须是 IMMUTABLE
(无论如何必须 return 相同参数的相同结果),否则索引可能会损坏.但是,ended_at
是 STABLE
而不是 IMMUTABLE
。
问题在您的原始设置中被“掩盖”了,因为 PostgreSQL 可以 内联 函数,即用函数定义替换函数调用。现在add_days
被标记为IMMUTABLE
,这样就可以了。
在函数上设置 search_path
后,函数不能再被内联,所以现在你得到错误信息。
我会说你也应该将 ended_at
声明为 IMMUTABLE
。
在 PostgreSQL 11 上,我定期转储备份快照,有时将它们导入具有相同设置的开发系统。没什么特别的:
# Dump
ps_dump -OU <user> <database> >dump.sql
# Restore
psql -U <user> -f dump.sql <database>
但是,转储中的两个索引在恢复时抛出错误。我将它归结为以下转储,并删除了所有不相关的内容:
--
-- PostgreSQL database dump
--
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
--
-- Name: add_days(timestamp without time zone, integer, text); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.add_days(timestamp without time zone, integer, text DEFAULT 'Europe/Zurich'::text) RETURNS timestamp without time zone
LANGUAGE sql IMMUTABLE
SET search_path TO 'public', 'pg_temp'
AS $_$
SELECT ((::timestamp AT TIME ZONE 'UTC' AT TIME ZONE + INTERVAL '1 day' * ) AT TIME ZONE )::timestamp
$_$;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: projects; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.projects (
id integer NOT NULL,
started_at timestamp without time zone,
duration integer
);
--
-- Name: ended_at(public.projects); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
AS $_$
SELECT add_days(.started_at, .duration)
$_$;
--
-- Name: index_projects_on_ended_at; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_projects_on_ended_at ON public.projects USING btree (public.ended_at(projects.*));
还原此转储会产生以下错误:
psql:dumped.sql:60: ERROR: function add_days(timestamp without time zone, integer) does not exist
LINE 2: SELECT add_days(.started_at, .duration)
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY:
SELECT add_days(.started_at, .duration)
CONTEXT: SQL function "ended_at" during inlining
以下解决了问题:使用架构前缀 add_days
如下所示:
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
AS $_$
SELECT public.add_days(.started_at, .duration)
$_$;
案件结案?不完全的。我想了解这里的问题是什么。
我猜,由 pg_dump 生成的行 SELECT pg_catalog.set_config('search_path', '', false);
会阻止 add_days
被发现,除非带有明确的模式前缀。
但是,为什么以下替代方法(添加 search_path)不起作用?
CREATE FUNCTION public.ended_at(public.projects) RETURNS timestamp without time zone
LANGUAGE sql STABLE
SET search_path TO 'public', 'pg_temp'
AS $_$
SELECT add_days(.started_at, .duration)
$_$;
这会触发一个完全不同的错误:
psql:dumped.sql:58: ERROR: functions in index expression must be marked IMMUTABLE
好吧,现在我很困惑。有人能告诉我这是怎么回事吗?
这里有两个不同的问题。
为什么恢复转储时出现错误?
问题是由修复了 PostgreSQL 安全问题的 this 补丁引起的。
在此更改之前,
pg_dump
/pg_restore
会将search_path
设置为如下所示:SET search_path = dumped_schema, pg_catalog;
问题在于索引定义(和其他地方)中使用的任何函数或运算符将首先在
dumped_schema
中搜索。恶意用户可以利用它在恢复期间以超级用户权限执行其功能。
你已经想到了解决问题的方法
这确实很烦人,但鉴于到目前为止您的功能受当前
search_path
设置的支配(任何人都可以使用简单的SET
命令更改),我会明白它本身就是一种改进。为什么对
SET search_path
函数不起作用?这里的问题是在索引中使用的任何函数都必须是
IMMUTABLE
(无论如何必须 return 相同参数的相同结果),否则索引可能会损坏.但是,ended_at
是STABLE
而不是IMMUTABLE
。问题在您的原始设置中被“掩盖”了,因为 PostgreSQL 可以 内联 函数,即用函数定义替换函数调用。现在
add_days
被标记为IMMUTABLE
,这样就可以了。在函数上设置
search_path
后,函数不能再被内联,所以现在你得到错误信息。我会说你也应该将
ended_at
声明为IMMUTABLE
。