Доступ к динамическому имени столбца типа строки в функции триггера - PullRequest
1 голос
/ 19 марта 2019

Я пытаюсь создать динамическую функцию для настройки триггеров.

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$$
  DECLARE
    devices_count INTEGER;
    table_name    regclass := TG_ARGV[0];
    column_name   VARCHAR  := TG_ARGV[1];
  BEGIN
    LOCK TABLE device_types IN EXCLUSIVE MODE;
    EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

    SELECT INTO devices_count device_types_count();

    IF TG_OP = 'DELETE' THEN
      SELECT format(
        'PERFORM validate_bid_modifiers_count(%s, %s, OLD.%s, %s)',
        table_name,
        column_name,
        column_name,
        devices_count
      );
    ELSE
      SELECT format(
        'PERFORM validate_bid_modifiers_count(%s, %s, NEW.%s, %s)',
        table_name,
        column_name,
        column_name,
        devices_count
      );
    END IF;
    RETURN NEW;
  END;
$$ LANGUAGE plpgsql;

Моя проблема связана с выполнением динамической функции validate_bid_modifiers_count(). В настоящее время выбрасывает:

ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function device_bid_modifiers_count_per() line 21 at SQL statement

Я не могу обернуть голову вокруг этого. Я понимаю, что format() возвращает правильную строку вызова функции с аргументами. Как мне это исправить и заставить работать?

Ответы [ 2 ]

2 голосов
/ 19 марта 2019

Это должно сделать это:

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$func$
DECLARE
   devices_count int      := device_types_count();
   table_name    regclass := TG_ARGV[0];
   column_name   text     := TG_ARGV[1];
BEGIN
   LOCK TABLE device_types IN EXCLUSIVE MODE;
   EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

   IF TG_OP = 'DELETE' THEN
      PERFORM validate_bid_modifiers_count(table_name
                                         , column_name
                                         , <b>(row_to_json(OLD) ->> column_name)::bigint</b>
                                         , devices_count);
   ELSE
      PERFORM validate_bid_modifiers_count(table_name
                                         , column_name
                                         , <b>(row_to_json(NEW) ->> column_name)::bigint</b>
                                         , devices_count);
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

Непосредственной причиной сообщения об ошибке была внешняя SELECT.Без цели вам нужно заменить ее на PERFORM в plpgsql.Но внутренняя PERFORM в строке запроса, переданная EXECUTE, также была неверной.PERFORM - это команда plpgsql, недопустимая в строке SQL, переданной в EXECUTE, которая ожидает код SQL.Вы должны использовать SELECT там.Наконец, OLD и NEW не видны внутри EXECUTE, и каждый из них вызовет свое собственное исключение, как у вас.Все проблемы устраняются путем удаления EXECUTE.

Простой и быстрый способ получения значения динамического имени столбца из типов строк OLD и NEW: приведение кjson, затем вы можете параметризовать имя ключа, как показано.Должно быть немного проще и быстрее, чем альтернатива с динамическим SQL - что также возможно, например:

  ...
  EXECUTE format('SELECT validate_bid_modifiers_count(table_name
                                                    , column_name
                                                    , <b>($1.%I)::bigint</b>
                                                    , devices_count)', <b>column_name</b>)
  <b>USING OLD</b>;
  ...

Связанный:

В сторону: не уверен, зачем нужны тяжелые блокировки.

В сторону 2: Вместо этого рассмотрите возможность написания отдельной функции триггера для каждого триггера.Более шумный DDL, но проще и быстрее в исполнении.

0 голосов
/ 20 марта 2019

Как я указал в комментарии к ответу Эрвина Брандштеттера , изначально у меня было почти идентичное решение.

Но проблема заключалась в том, что я получал ошибку

ERROR: record "new" has no field "column_name"
CONTEXT: SQL statement "SELECT validate_bid_modifiers_count(table_name, column_name, NEW.column_name, devices_count)"
PL/pgSQL function device_bid_modifiers_count_per() line 15 at PERFORM

Вот почему я подумал, что мне нужен способ для динамической оценки вещей.

В настоящее время это работает со следующим все еще уродливым решением (уродливым, потому что мне не нравятся 2 IF утверждения,Мне бы хотелось, чтобы он был супер-динамичным, но, возможно, я слишком многого прошу):

CREATE OR REPLACE FUNCTION device_bid_modifiers_count_per()
  RETURNS TRIGGER AS
$func$
  DECLARE
    row           RECORD;
    table_name    regclass := TG_ARGV[0];
    column_name   text := TG_ARGV[1];
    devices_count INTEGER;

  BEGIN
    LOCK TABLE device_types IN EXCLUSIVE MODE;
    EXECUTE format('LOCK TABLE %s IN EXCLUSIVE MODE', table_name);

    devices_count := device_types_count();

    IF TG_OP = 'DELETE' THEN
      row := OLD;
    ELSE
      row := NEW;
    END IF;

    IF column_name = 'campaign_id' THEN
      PERFORM validate_bid_modifiers_count(table_name, column_name, row.campaign_id, devices_count);
    ELSIF column_name = 'adgroup_id' THEN
      PERFORM validate_bid_modifiers_count(table_name, column_name, row.adgroup_id, devices_count);
    ELSE
      RAISE EXCEPTION 'invalid_column_name %', column_name;
    END IF;
    RETURN NEW;
  END;
$func$ LANGUAGE plpgsql;

Я открыт для более надежных предложений решения.

По сути, второе условие вида 'Я почти отказался от цели иметь одну функцию, я мог бы на этом этапе разделить ее на две функции.Поскольку цель состоит в том, чтобы определить несколько (2) триггеров, используя эту функцию (предоставив ей аргументы).

...