Синтаксическая ошибка на уровне $ 1 или около него при попытке изменить тип в postgreSQL-9.6

Я пытаюсь добавить значение перечислимому типу в postgreSQL-9.6, и мне сложно понять, что я делаю неправильно.

var tc = new NpgsqlCommand(@"ALTER TYPE attributeName ADD VALUE IF NOT EXISTS 
:a", conn);

//tc.Parameters.Add(new NpgsqlParameter("a", NpgsqlDbType.Text));
//tc.Parameters[0].Value = "test";
tc.Parameters.AddWithValue("a", NpgsqlDbType.Text, "test");

tc.ExecuteNonQuery();

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

$exception  {"42601: syntax error at or near \"$1\""}

Я понимаю, что $ 1 относится к тексту, передаваемому через параметр «a», но я не понимаю, почему возникает проблема или как ее исправить. В документации PostgreSQL говорится, что ALTER TYPE нельзя выполнить в блоке транзакции, но, насколько я могу судить, Npgsql не запускает транзакции автоматически, так что это не должно быть проблемой. Если я выполню другую команду SQL с аналогичным синтаксисом, например:

var tc = new NpgsqlCommand(@"INSERT INTO test VALUES (:a)", conn);

программа работает безотказно. Кроме того, если я наберу команду непосредственно в оболочке psql, например:

ALTER TYPE attributeName ADD VALUE IF NOT EXISTS 'test';

он работает как положено. Может ли кто-нибудь помочь мне понять, что я делаю не так? Спасибо.


person dellaint    schedule 16.05.2017    source источник
comment
Это не похоже на ваш реальный код, если у вас действительно нет атрибута с именем attributeName.   -  person 500 - Internal Server Error    schedule 17.05.2017
comment
На самом деле, это относится к атрибуту предмета в игре, поэтому для меня имело смысл назвать это так. Я не уверен, что вы предлагаете это ключевое слово и вызовет проблемы из-за этого, или это просто плохая практика называть его так. Я постараюсь придумать имя получше. Может быть, modName или traitName?   -  person dellaint    schedule 17.05.2017
comment
Нет-нет, все в порядке.   -  person 500 - Internal Server Error    schedule 17.05.2017


Ответы (3)


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

Однако вы можете использовать для него замену строки:

string name = "test";
var tc = new NpgsqlCommand($"ALTER TYPE attributeName ADD VALUE IF NOT EXISTS '{name}'", conn);

Имейте в виду, что это небезопасный подход!

ОБНОВЛЕНИЕ:

Другое решение - использование хранимой процедуры, выполняющей команду. Однако вы не можете просто вызвать команду ALTER TYPE ... ADD VALUE ... внутри процедуры / функции, потому что она не работает внутри блоков транзакций. Вы получите следующую ошибку:

«ALTER TYPE ... ADD нельзя выполнить из функции или строки с несколькими командами»

Эта цепочка должна пролить свет на проблему: Проблемы с типом манипуляции с ENUM 9.1

Однако у вашей проблемы может быть решение. Похоже, вы пытаетесь изменить существующий тип перечисления (добавить новое значение). Вы можете создать функцию, которая работает напрямую с pg_enum таблицей. Набор функций базы данных, предназначенный для работы с перечисляемыми типами, можно найти здесь: PostgreSQL 8.3+, 9.1+ ALTER Эмуляция ENUM: добавление / удаление элементов, транзакции.

Функция, которая добавляет новое значение к существующему перечислению, выглядит следующим образом:

-- Also works within transactions in PostgreSQL 9.1+ (but you need
-- to reconnect to the database after transaction commit, because 
-- new enum items are not be visible within the session you used
-- to add them).
--
-- See http://en.dklab.ru/lib/dklab_postgresql_enum/
--
-- (C) Dmitry Koterov, 2013
-- This code is BSD licensed.
--

CREATE SCHEMA enum AUTHORIZATION postgres;


SET search_path = enum, pg_catalog;
SET check_function_bodies = false;
CREATE OR REPLACE FUNCTION enum.enum_add (
  enum_name varchar,
  enum_elem varchar
)
RETURNS void AS
$body$
DECLARE
    eoid OID;
    has_sortorder BOOLEAN;
BEGIN
    eoid := (
        SELECT pg_type.oid
        FROM pg_type JOIN pg_namespace ON pg_namespace.oid=pg_type.typnamespace
        WHERE typtype='e' AND enum_name IN(typname, nspname||'.'||typname)
    );
    has_sortorder := EXISTS(
        select 1
        from pg_attribute
        where attrelid=(select oid from pg_class where relname='pg_enum') and attname='enumsortorder'
    );
    IF has_sortorder THEN
        EXECUTE '
            INSERT INTO pg_enum(enumtypid, enumlabel, enumsortorder) VALUES(
                '||eoid||',
                '||quote_literal(enum_elem)||',
                (SELECT MAX(enumsortorder) + 1 FROM pg_enum WHERE enumtypid='||eoid||')
            )
        ';
    ELSE
        EXECUTE E'INSERT INTO pg_enum(enumtypid, enumlabel) VALUES('||eoid||', '||quote_literal(enum_elem)||')';
    END IF;
END;
$body$
    LANGUAGE 'plpgsql';

COMMENT ON FUNCTION enum.enum_add (enum_name character varying, enum_elem character varying) IS 'Inserts a new ENUM element wthout re-creating the whole type.';

Теперь вы можете просто вызвать хранимую процедуру / функцию из своего кода C #:

using (var cmd = connection.CreateCommand())
{
    cmd.CommandText = @"enum.enum_add";
    cmd.Parameters.AddWithValue("enum_name", "attributeName");
    cmd.Parameters.AddWithValue("enum_elem", "O'Reilly");
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.ExecuteNonQuery();
}

Обратите внимание, что указанная выше функция не сработает, если вы попытаетесь добавить существующее значение.

person Łukasz Mieczkowski    schedule 17.05.2017
comment
Я пробовал это, но в строках, которые я ввожу, есть символы, которые нужно экранировать, например ', и я не совсем уверен, как с этим справиться. Я надеялся, что решить основную проблему будет проще, чем выполнить замену строки. Чтобы было ясно, это работает для строк без символов, которые необходимо экранировать. - person dellaint; 17.05.2017
comment
Похоже, замена строк - мой лучший вариант. Чтобы решить мою проблему с экранированием одинарных кавычек, я использую inputString.Replace(@"'", @"''"). В данном случае я получаю данные из API, а не от пользователей, поэтому мне не нужно беспокоиться о SQL-инъекции, но другие пользователи могут захотеть использовать более надежный метод экранирования символов. - person dellaint; 18.05.2017

У меня нет опыта работы с npgsql, хотя, возможно, это совершенно не так, но NpgsqlCommand , похоже, выполняет подготовленный оператор . Если это так, вы не можете подготовить оператор ALTER:

заявление

Любой оператор SELECT, INSERT, UPDATE, DELETE или VALUES.

person Vao Tsun    schedule 17.05.2017
comment
Я думаю, вам действительно нужно использовать command.Prepare();, чтобы подготовить команду, поэтому, хотя вы не сможете подготовить оператор ALTER, я не думаю, что это проблема. По умолчанию, насколько я могу судить, Npgsql использует неподготовленные команды. Это также подтверждается тем фактом, что ввод команды путем замены строки, как это предлагает mieczyk, действительно работает. - person dellaint; 17.05.2017

PostgreSQL не поддерживает заполнители параметров везде, и, если я не ошибаюсь, он специально не поддерживается в операторах DDL, таких как ALTER TYPE. Скорее всего, вам придется вставить желаемое значение в качестве литерала в свой оператор (обязательно примите во внимание SQL-инъекцию).

person Shay Rojansky    schedule 17.05.2017
comment
Мне нужно иметь дело только с одинарными кавычками в моих строках, потому что они не предоставляются пользователем. Есть ли лучший способ справиться с экранированием одинарных кавычек, чем inputString.Replace(@"'", @"''")? - person dellaint; 18.05.2017