Есть ли способ заставить Delphi FireDAC распознавать позиционные параметры PostgreSQL, сгенерированные FireDAC?

Я выполняю запросы с именованными параметрами из FireDAC в PostgreSQL 11, используя собственный драйвер FireDAC Postgres. Во время оператора подготовки FireDAC преобразует именованные параметры в позиционные параметры, что правильно. Однако, если я затем попытаюсь присвоить значения этим параметрам, FireDAC выдаст исключение «Аргумент вне диапазона». Похоже, что FireDAC не распознает сгенерированные им позиционные параметры. Например, если исходный текст SQL выглядел примерно так:

SELECT * FROM account WHERE accountid = :accid;

при вызове метода Prepare FDQuery FireDAC преобразовал этот запрос в следующий:

SELECT * FROM account WHERE accountid = $1;

Но когда я пытаюсь присвоить значение параметру, я получаю сообщение об ошибке. Задание выглядит примерно так:

FDQuery1.Params[0].AsString = strID;

где strID — строковое значение, а accountid — текстовое поле. Кроме того, если я использую что-то вроде следующего, он возвращает 0.

ShowMessage( IntToStr( FDQuery1.Params.Count ) );

Я значительно упростил этот код, но проблемы остались прежними. Как мне заставить FireDAC распознавать сгенерированные им позиционные параметры?


Обновление: как я уже говорил, приведенный выше код значительно упрощен. Что на самом деле происходит, так это то, что в нашей структуре у нас есть один набор подпрограмм, которые присваивают значения макросам FireDAC, а затем мы генерируем оператор SQL, подготавливая запрос и затем считывая свойство Text FDQuery. Этот оператор SQL затем присваивается свойству SQL.Text другого FDQuery (также динамически созданного), и именно там запрос завершается ошибкой. Итак, вот очень простой пример того, что происходит внутри кода:

var
  Query: TFDQuery;
begin
  Query := TFDQuery.Create( nil );
  Query.Connection := PGConnection;
  // In reality, the SQL statement below was generated earlier,
  // from a function call where the SQL was created by the FireDAC
  // SQL preprocessor, as opposed to being a literal expression
  Query.SQL.Text := 'SELECT * FROM tablename WHERE field2 = $1;';
  Query.Params[0].AsString := '4'; // BANG! Argument out of range

Я подумал, что это может быть связано с расширением макроса FireDAC, поэтому я добавил следующие две строки после создания экземпляра FDQuery:

Query.ResourceOptions.MacroCreate := False;
Query.ResourceOptions.MacroExpand := False;

Неа. Это тоже помогло. Я предполагаю, что FireDAC просто не распознает, что $1 является допустимым позиционным параметром в PostgreSQL.


person Cary Jensen    schedule 08.07.2019    source источник
comment
Как вы определяете параметры FireDac? Что произойдет, если вы попытаетесь использовать ParamByName вместо порядкового номера?   -  person JacalarRick    schedule 08.07.2019
comment
После оператора подготовки параметры не имеют имени. На самом деле, что касается FireDAC, параметров нет (Params.Count = 0).   -  person Cary Jensen    schedule 08.07.2019
comment
Можете ли вы показать полный минимальный набор кода Delphi, который дублирует эту проблему? Вы вызываете FDQuery1.Prepare перед объявлением своих параметров? FDQuery1 определяется в коде или во время разработки?   -  person JacalarRick    schedule 09.07.2019
comment
JacalarRick: В исходном коде FDQuery создается «на лету» и назначается общему FDConnection. Также обратите внимание, что ResourceOptions ParamCreate и ParamExpand имеют значение True (по умолчанию) как для FDConnection, так и для FDQuery.   -  person Cary Jensen    schedule 09.07.2019
comment
И да, я работаю над простым примером, демонстрирующим проблему, хотя к тому времени я думаю, что идентифицирую источник ошибки.   -  person Cary Jensen    schedule 09.07.2019


Ответы (2)


Вам нужно указать символический идентификатор параметра (в вашем случае :accid) в строке SQL.Text, а не позиционный код ($1)

Я только что проверил эти два варианта. Первый работает, второй нет.

var
  MyQ : tfdquery;
begin
  MyQ := Tfdquery.Create(nil);
  MyQ.Connection := dm1.dbMain;
  MyQ.SQL.Text := 'Select * from person where lastname = :lname;';
  MyQ.Params[0].AsString := 'Brodzinsky';
  MyQ.Open();
  ShowMessage('Records found = '+MyQ.RecordCount.ToString);
  MyQ.Close;
  MyQ.Free;
end;

Следующий пытается использовать position. Конечно, FireDac не видит двоеточие, поэтому не знает, что есть параметр для создания.

var
  MyQ : tfdquery;
begin
  MyQ := Tfdquery.Create(nil);
  MyQ.Connection := dm1.dbMain;
  MyQ.SQL.Text := 'Select * from person where lastname = $1;';
  MyQ.Params[0].AsString := 'Brodzinsky';
  MyQ.Open();
  ShowMessage('Records found = '+MyQ.RecordCount.ToString);
  MyQ.Close;
  MyQ.Free;
end;

В первом случае 11 записей в таблице лиц возвращаются с моей фамилией; во втором генерируется ошибка Argument Out Of Range, так как в тексте SQL не указан параметр. Примечание. Я обращаюсь к базе данных MySQL, но проблема заключается в предварительной обработке Delphi и FireDac кода для отправки на сервер базы данных.

person JacalarRick    schedule 10.07.2019
comment
Я получаю текст SQL из другого метода, который выполняет некоторую привязку макросов и генерирует SQL из FireDAC, который назначается FDQuery в другом методе. FireDAC заменяет :acctid на $1, а не я. В исходном FDQuery, используемом для генерации SQL, FireDAC понимал, что $1 — это параметр, но когда я беру этот SQL и назначаю его другому FDQuery, FireDAC не знает, что $1 — это параметр. Я надеялся, что в FireDAC есть какое-то свойство, которое позволит мне сообщить FireDAC, что $1 является параметром. Я думаю, что у меня есть решение, которое я опубликую, как только протестирую его. - person Cary Jensen; 11.07.2019

Хорошо, может быть способ решить эту проблему с помощью свойств FireDAC, но я пока его не нашел. Однако для этой конкретной ситуации, когда SQL подготавливается одним методом, а затем назначается другому FDQuery из другого метода, я нашел ответ. Поскольку PostgreSQL позволяет использовать в именованных параметрах числа, такие как :1, я заменил символы $ на символы :. Как в

var
  SQLStmt: String;
  FDQuery: TFDQuery;
begin
  SQLStmt :=  'SELECT * FROM tablename WHERE field2 = $1;';
  FDQuery := TFDQuery.Create( nil );
  FDQuery.Connection := FDConnection1;
  FDQuery.SQL.Text := SQLStmt.Replace( '$', ':' );
  FDQuery.Params[0].AsString := 'SomeValue'); // Works!
  ...

И да, если ваш запрос включает более одного экземпляра именованного параметра, FireDAC заменяет его тем же числовым.

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

person Cary Jensen    schedule 11.07.2019
comment
Поскольку $ не является специальным символом в SQL, он может появляться где угодно, в зависимости от имен таблиц, имен столбцов или даже литералов. Хотя это решение (которое на самом деле просто поменяло местами символы, как только я указал, что $1 не является параметром) устранило вашу непосредственную проблему, если вы не на 100% отвечаете за схему, я бы не стал на нее полагаться. - person JacalarRick; 12.07.2019
comment
ДжакаларРик: Вы правы. К счастью, в этом случае я на 100% отвечаю за схему. - person Cary Jensen; 14.08.2019