PostgreSQL - динамические таблицы, вставка значений из текстового массива в функцию - PullRequest
1 голос
/ 06 февраля 2012

Клиент хочет хранить что-либо в отдельных таблицах. (Длинная история, это необходимо). Для этого я построил функцию Postgres для создания новых таблиц на лету в своем собственном пространстве имен.

Эти таблицы могут иметь 2, 4 или 100 столбцов, как раз то, что хочет пользователь. Нет проблем, это работает. Используемые типы данных в этих динамических таблицах являются нативными, например, текст, логические значения, целые числа и так далее.

Теперь возникает проблема, я должен вставить данные в эти таблицы. Дело в том, что пользователи не могут получить доступ к таблицам напрямую, они будут делать это через функцию.

Для пары типов данных это не проблема, но для текстовых типов данных это проблематично.

Вот функция до сих пор:

-- Function: add(integer, text[])

-- DROP FUNCTION add(integer, text[]);

CREATE OR REPLACE FUNCTION add(id integer, fields text[])
  RETURNS integer AS
$BODY$
DECLARE
l_line_ending text := ')';
  l_fieldtype integer;
  l_ito_table_name text;
  l_ito_fieldnames text;
  l_field ito_fields%rowtype;
  l_first_loop boolean := true;
  l_values_to_insert text := 'VALUES (';
  l_loop_counter integer := 0;
  l_query text;

BEGIN
  select into l_ito_table_name ito_table_name from ito where id = target_ito_id;
  l_ito_fieldnames := 'insert into ' || l_ito_table_name || '(';
    FOR l_field IN SELECT * FROM ito_fields
    WHERE ito_fields.ito_id = target_ito_id
    order by ito_fields.id asc
    LOOP
        l_loop_counter := l_loop_counter +1;
        l_fieldtype := l_field.fieldtype;
        if not l_first_loop THEN
            l_values_to_insert := (l_values_to_insert || ', ');
        end if;
        if l_field.fieldtype = 1 THEN
             l_values_to_insert := (l_values_to_insert || '''' || (fields[l_loop_counter]) || '''' );
        elsif l_field.fieldtype = 2 THEN
            l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_integer(fields[l_loop_counter]));
        elsif l_field.fieldtype = 3 THEN
            l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_boolean(fields[l_loop_counter]));
        elsif l_field.fieldtype = 4 THEN
            l_values_to_insert := quote_literal(l_values_to_insert || private.cast_to_float(fields[l_loop_counter]));
        else 
            return 103;
        end if;
        if l_first_loop then
            l_ito_fieldnames := l_ito_fieldnames || l_field.column_name;
            l_first_loop := false;
        else
            l_ito_fieldnames := l_ito_fieldnames || ', ' || l_field.column_name;
        end if;
    END LOOP;
    l_ito_fieldnames := l_ito_fieldnames || l_line_ending;
    l_values_to_insert := ((l_values_to_insert) || (l_line_ending));
    l_query := (l_ito_fieldnames || l_values_to_insert);
    EXECUTE l_query;
  return 0;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION add(integer, text[])
  OWNER TO postgres;

В таблице ito_fields хранятся все метаданные поля, поэтому здесь хранятся тип данных, версии, описания. в таблице ito хранятся все данные динамической таблицы.

Точка в этой функции - кавычки. Функция вставки создается динамически, поэтому мне нужно добавить несколько кавычек вокруг текстовых полей в функции вставки. Postgres дает ошибки, как только я это делаю. Даже с функциями quote_literal это все еще проблема из-за конкатенации строк (я знаю, риски безопасности, но пока это не проблема).

Я пытался использовать quote_literal, quote_ident, даже заменяя кавычки (') заменой, пока не выполнится функция execute (replace (query, l_quote_rep,' '' ')). У меня действительно нет подсказки теперь как это исправить ...

Заранее спасибо.

1 Ответ

1 голос
/ 08 февраля 2012

Массивы , агрегаты , quote_ident и quote_nullable ваши друзья.

Это должно работать, а код короче:

CREATE OR REPLACE FUNCTION add(id integer, fields text[])
  RETURNS integer AS
$BODY$
DECLARE
  l_ito_table_name text;
  l_query text;
  l_fields text;
  r_values text;
BEGIN
    --get table name
    SELECT INTO l_ito_table_name quote_ident(ito_table_name) FROM ito WHERE id = target_ito_id;
    -- get column names
    SELECT INTO l_fields
    array_to_string( array_agg(quote_ident(column_name)), ',' )
    FROM ito_fields
    WHERE ito_fields.ito_id = target_ito_id
    order by ito_fields.id asc;
    -- prepare values
    SELECT INTO r_values
    array_to_string( array_agg(quote_nullable(u.name)), ',' )
    FROM unnest(fields) u(name);
    l_query := 'insert into ' || l_ito_table_name || '(' || l_fields || ') values (' || r_values || ')';
    EXECUTE l_query;
    return 0; -- why 0?
END;
$BODY$;
...