【问题标题】:How should I extract duplicated logic in a Postgres function?我应该如何在 Postgres 函数中提取重复的逻辑?
【发布时间】:2016-05-13 04:23:00
【问题描述】:

我有一个包含大量重复逻辑的 Postgres 函数。如果我用 Ruby 等语言编写它,我会将重复的逻辑提取到一些私有辅助方法中。但是在 Postgres 中似乎没有等效的“私有方法”。

原始函数

CREATE OR REPLACE FUNCTION drop_create_idx_constraint(in_operation varchar, in_table_name_or_all_option varchar)  RETURNS integer AS $$
DECLARE
    cur_drop_for_specific_tab CURSOR (tab_name varchar) IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
    cur_drop_for_all_tab CURSOR IS SELECT drop_stmt FROM table_indexes;

    cur_create_for_specific_tab CURSOR (tab_name varchar) IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
    cur_create_for_all_tab CURSOR IS SELECT recreate_stmt FROM table_indexes;

BEGIN

  IF upper(in_operation) = 'DROP' THEN
    IF upper(in_table_name_or_all_option) ='ALL' THEN
      FOR table_record IN cur_drop_for_all_tab LOOP
        EXECUTE table_record.drop_stmt;
      END LOOP;

    ELSE
      FOR table_record IN cur_drop_for_specific_tab(in_table_name_or_all_option) LOOP
        EXECUTE table_record.drop_stmt;
      END LOOP;
    END IF;
  ELSIF upper(in_operation) = 'CREATE' THEN
    IF upper(in_table_name_or_all_option) ='ALL' THEN
      FOR table_record IN cur_create_for_all_tab LOOP
        EXECUTE table_record.recreate_stmt;
      END LOOP;
    ELSE
      FOR table_record IN cur_create_for_specific_tab(in_table_name_or_all_option) LOOP
        EXECUTE table_record.recreate_stmt;
      END LOOP;
    END IF;
  END IF;
    RETURN 1;
END;
$$ LANGUAGE plpgsql;

重构函数

CREATE OR REPLACE FUNCTION execute_recreate_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
  BEGIN
    FOR table_record IN input_cursor LOOP
      EXECUTE table_record.recreate_stmt;
    END LOOP;
    RETURN 1;
  END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION execute_drop_stmt_from_records(input_cursor refcursor) RETURNS integer AS $$
  BEGIN
    FOR table_record IN input_cursor LOOP
      EXECUTE table_record.drop_stmt;
    END LOOP;
    RETURN 1;
  END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION drop_indexes_and_constraints(table_name_to_drop varchar) RETURNS integer AS $$
  DECLARE
    indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_drop;
  SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION drop_all_indexes_and_constraints() RETURNS integer AS $$
  DECLARE
    indexes_and_constraints CURSOR IS SELECT drop_stmt FROM table_indexes;
  SELECT execute_drop_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION recreate_indexes_and_constraints(table_name_to_recreate varchar) RETURNS integer AS $$
  DECLARE
    indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes WHERE table_indexes.table_name = table_name_to_recreate;
  SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION recreate_all_indexes_and_constraints() RETURNS integer AS $$
  DECLARE
    indexes_and_constraints CURSOR IS SELECT recreate_stmt FROM table_indexes;
  SELECT execute_recreate_stmt_from_records(indexes_and_constraints);
$$ LANGUAGE plpgsql;

我相信我的重构的根本问题是辅助函数 execute_recreate_stmt_from_recordsexecute_drop_stmt_from_records 功能太强大而无法公开访问,特别是因为 Heroku(托管此 DB)只允许一个 DB 用户。当然,如果上述重构还有其他问题,欢迎指出。

【问题讨论】:

  • 在 Postgres 的 Create Function 文档中,有关于 SECURITY DEFINER 的讨论。我从未使用过它,但它可能是您正在寻找的保护这些功能不被全局化的东西。
  • 来自PG docs:“SECURITY INVOKER 表示该函数将以调用它的用户的权限执行。这是默认值。SECURITY DEFINER 指定要执行该函数具有创建它的用户的权限。”这些选项似乎都没有将函数限制为仅由这些其他函数调用。

标签: postgresql stored-procedures heroku encapsulation heroku-postgres


【解决方案1】:

您可以通过将“私有”过程移动到新架构中来实现分离,从而限制对其的访问。然后使用 SECURITY DEFINER 允许调用“私有”函数。

不过,如果您的托管服务仅限于单个用户,这将很难实现。

例子:

CREATE USER app_user;
CREATE USER private_user;

GRANT ALL ON DATABASE my_database TO app_user;
GRANT CONNECT, CREATE ON DATABASE my_database TO private_user;

-- With private_user:
CREATE SCHEMA private;

CREATE OR REPLACE FUNCTION private.test_func1()
    RETURNS integer AS
$BODY$
BEGIN
    RETURN 123;
END
$BODY$
    LANGUAGE plpgsql STABLE
    COST 100;

CREATE OR REPLACE FUNCTION public.my_function_1()
    RETURNS integer AS
$BODY$
DECLARE

BEGIN
    RETURN private.test_func1();
END
$BODY$
    LANGUAGE plpgsql VOLATILE SECURITY DEFINER
    COST 100;

-- With app_user:
SELECT private.test_func1();  -- ERROR: permission denied for schema private
SELECT my_function_1();       -- Returns 123

【讨论】:

  • 哇,我已经放弃了任何人回答这个问题的希望,谢谢!听起来 SECURITY DEFINER 确实是处理这个问题的最佳方式。因此,如果我有 许多 函数,每个函数都有不同的“私有”过程,这是否意味着我需要为每个函数定义不同的用户?这是 Postgres 的常见做法吗?
  • 不,只需创建一个用户,该用户将成为所有“私人”功能的创建者。我会扩展我的答案来举个例子。
猜你喜欢
  • 2017-03-03
  • 1970-01-01
  • 2023-01-16
  • 2012-08-16
  • 2011-06-01
  • 1970-01-01
  • 2011-09-08
  • 1970-01-01
  • 2021-11-16
相关资源
最近更新 更多