TMSQuery.Open портит память

Обсуждение возникших проблем, предложений и ошибок SDAC компонентов
saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

TMSQuery.Open портит память

Сообщение saupg » Вт 06 май 2014 07:28

Здравствуйте!
Случайно обнаружил, что тут есть русскоязычный форум, а я всё свой английский мучаю:
http://forums.devart.com/viewtopic.php?f=6&t=29501

Попробую изложить здесь ту же проблему.

Итак, пытаюсь перевести проект со старого SDAC 5 версии на самый "свежак" (6.10). Даже подписку уже обновил (Developer Number CRSDA-03066).

В проекте есть сервер приложений (COM+). На новом SDAC полезло множество глюков - клиентское приложение зависает, сервер в логи сыпет кучи сообщений об ошибках, среди которых "Communication link failure" - самое пристойное.

В отладчике Delphi исключение всегда возникает на открытии определённых наборов данных (TMSQuery.Open) но только не для простых запросов. По наблюдениям, ошибка возникает на больших запросах с использованием специфических инструкций T-SQL ("WITH - общие табличные выражения", OUTER\CROSS APPLY, большое количество соединений таблиц).

После целого дня мучений я всё-таки научился воспроизводить эту ситуацию!

Я использую Delphi XE и SQL Server 2005 SP4:

Код: Выделить всё

Microsoft SQL Server 2005 - 9.00.5292.00 (X64) 
Apr 13 2011 15:43:31 
Copyright (c) 1988-2005 Microsoft Corporation
Developer Edition (64-bit) on Windows NT 5.2 (Build 3790: Service Pack 2)
Глюк воспроизводится стабильно только на определённых таблицах. Изменение количества полей, наличие первичного ключа в таблице запросто влияют на результат. Я подобрал для своего примера вот такую таблицу:

Код: Выделить всё

CREATE TABLE [dbo].[SDAC_BUG](
  ID SMALLINT NOT NULL,
  ID_PARENT SMALLINT NOT NULL
  CONSTRAINT [PK_SDAC_BUG] PRIMARY KEY (ID, ID_PARENT)
)
На форме надо разместить компоненты TMSConnection и TMSQuery. Все свойства оставить по умолчанию. Было сложно, но удалось создать простой тестовый запрос для демонстрации проблемы:

Код: Выделить всё

procedure TForm1.Button1Click(Sender: TObject);
const
  qTest =
    'WITH TEST (ID) AS ( ' +
    '  SELECT A.ID FROM SDAC_BUG A ' +
    '  LEFT JOIN SDAC_BUG B ON 1 = 2 ' +
    ') ' +
    'SELECT T.ID FROM SYS.OBJECTS GP ' +
    'LEFT JOIN TEST T ON 1 = 2';
begin
  MSConnection1.Open;
  MSQuery1.SQL.Text := qTest;
  MSQuery1.Open; // <--- Exception!
  MSConnection1.Close;
end;
Для этого запроса я получаю такое исключение: "Exception class EOLEDBError with message 'Ошибка протокола в потоке TDS'.".

Любое небольшое изменение текста запроса может как изменить поведение процесса, так и исправить ошибку.
Например, если в последней строке запроса изменить "LEFT JOIN" на "FULL JOIN", то получаем ошибку: "Exception class EOLEDBError with message 'Соединение больше нельзя использовать, так как ответ сервера на выполнявшуюся ранее инструкцию имел неправильный формат.'".

А если там же использовать "INNER JOIN", то ошибки нет. Так же ошибка пропадает при изменении таблицы, например, достаточно из первичного ключа исключить один из столбцов.

Просьба не смотреть на "бредовость" тестового запроса. Я просто не могу понять, что же конкретно влияет на появление ошибки. Проблема очень похожа на изменение чужой памяти по невалидному указателю. Исправьте, пожалуйста.

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Вт 06 май 2014 16:07

Ответ на ваш вопрос вы можете прочитать в параллельном топике
http://forums.devart.com/viewtopic.php?t=29501

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Ср 07 май 2014 06:49

Да, "Options.UniqueRecords := False" решает проблему.
Спасибо!!!

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Ср 07 май 2014 07:11

Оказывается, если свойство UniqueRecords = False, то перед изменением данных в датасете надо обязательно вызывать метод Prepare (или устанавливать свойство Options.AutoPrepare := True, которое по-умолчанию = False). В документации про это ничего не сказано. Да и поведение какое-то нелогичное.

Пример кода для предыдущего проекта:

Код: Выделить всё

procedure TForm1.Button2Click(Sender: TObject);
const
  qTest = 'SELECT * FROM SDAC_BUG';
begin
  MSConnection1.Open;
  MSQuery1.Options.UniqueRecords := False;
  MSQuery1.SQL.Text := qTest;
  // MSQuery1.Prepare;
  MSQuery1.Open;
  MSQuery1.Insert; // <--- Exception
  MSConnection1.Close;
end;
С закомментаренной строкой MSQuery1.Prepare получаю ошибку "Exception class EDatabaseError with message 'MSQuery1: Cannot modify a read-only dataset'.".

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Ср 07 май 2014 14:13

Для того, чтобы поведение новой версии SDAC было аналогично старой версии TMSQuery.Options.UniqueRecords необходимо установить в True. При этом проблема при выполнении запроса из первого поста будет воспроизводиться как в старой, так и в новой версии. Проверьте, воспроизводится ли у вас проблема при выполнении запроса из первого поста на старой версии SDAC.

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Чт 08 май 2014 08:38

Да, что-то не везёт мне на примеры. Действительно, на 5-й версии пример из первого поста так же не работает, при этом свойство UniqueRecords на ситуацию никак не влияет.
Но, поверьте на слово, у меня реальные запросы в проекте, не работающие на 6-ой версии, прекрасно выполняются на 5-ой. Уж не знаю, в чём там отличие, но копать глубже нет времени.
Надесь, вы исправили именно эту ошибку :)

Кстати, на 5-ой версии при UniqueRecords := False не требуется обязательной подготовки запроса через вызов Prepare. Если вы говорите, что в 6-й версии так будет только при UniqueRecords := True, то мне обязательно нужен патч. Информация о лицензии приведена в первом посте. Пришлите исправление, пожалуйста.

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Пн 12 май 2014 15:39

Если вы хотите получить это исправление, пришлите ваш адрес электронной почты и номер лицензии на andreyz*devart*com, и мы вышлем вам билд с исправлением.

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Пн 30 июн 2014 09:55

Так, вернёмся к нашей проблеме (отвлёкся на переход с Delphi XE на XE6 и адаптацию под 64 бита).
Обновился на билд SDAC 6.10.20. По факту - проблема из первого поста и проблема с символом "/" (из другой ветки) остались на месте. Судя по "revision history" эти изменения должны были попасть в сборку. Почистил все dcu, проверил пути и настройки тестового проекта. Ничего не работает :( Пробовал на Delphi XE и XE6.
Прошу проверить, попали ли все необходимые изменения в сборку 6.10.20?

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Вт 01 июл 2014 12:42

Мы еще раз проверили и убедились, что в SDAC 6.10.20 вошли следующие исправления для:
- возникновение access violation во время открытия датасета, когда таблица, которая участвовала в SQL запросе, имела первичный ключ более, чем из одного поля.
- ошибка парсинга SQL скрипта, когда знак деления "/" был расположен отдельно на одной строке и ошибочно воспринимался как разделитель.

Похоже, что у вас на компьютере остались старые DAC юниты. Чтобы решить проблему, сделайте следующее:
- деинсталлируйте SDAC;
- найдите все DAC юниты, такие как CRParser.dcu, CLRClasses.dcu, MemData.dcu, MemDS.dcu, DBAccess.dcu, OLEDBClasses.dcu на вашем компьютере и удалите их;
- найдите все *dac*.bpl и удалите.
- установите SDAC.

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Вт 01 июл 2014 14:18

Эх, проверил и почистил всё ещё раз, полностью уверен что тестовый проект компилируется из исходников SDAC версии 6.10.20. Работаю с Delphi с 98-го года, все подлянки с dcu знаю.

Сравнил исходники 6.10.19 и 6.10.20.
Да, вижу сделанные вами изменения, например, в модуле DAScript.pas по поводу разделителя '/'. Вижу новую функцию SlashIsDelimiter и место её использования (547-ая строка), пошагово отлаживаю это место.

Код: Выделить всё

Ready := (FDelimiterState = dsSet) or (Code = lcEnd) or GetReady(Code);
if not Ready and (Code <> lcString) then begin
  Ready := CheckDelimeter and not IsSpecificSQL(StatementType);
  if not Ready and (FCurrDelimiter = FOwner.FDelimiter{';'}) and (FSt = '/') then begin
    if not SlashIsDelimiter then
      BlankLine := FParser.PrevCol = 0;
    if BlankLine then begin
      TempCode := FParser.GetNext(TempSt);
      if (TempCode = lcEnd) or
        (TempCode = lcBlank) and ((Pos(#13, TempSt) > 0) or (Pos(#10, TempSt) > 0))
      then
        Ready := True
      else
        FParser.Back;
    end;
  end;
end;
При встрече с символом '/' флаг BlankLine получается равным True, и затем TempCode равно lcBlank. В итоге флаг Ready получается True, и цикл разбора выражения прерывается, хотя в скрипте на следующей строке идёт продолжение (значение-делитель).
Тестовый скрипт всё тот же:

Код: Выделить всё

MSScript.SQL.Text := 'SELECT (10'#10#13 +
  '/'#10#13 +
  '2) AS RES';
MSScript.Execute;
Далее, не могу точно знать, куда смотреть в исходниках по поводу основной проблемы, изменений довольно много. В том фикс-билде, что вы передавали мне по почте, я вижу изменения в модуле DBAccess.pas (в функции TDADataSetService.GetDBKeyList). Но в билде 6.10.20 этих изменений почему-то нет... Хотя и фикс-билд на тестовом примере тоже даёт такие же ошибки.

Из всего вышесказанного я всё-таки делаю вывод, что по одному вопросу исправлено что-то не до конца, а по другому исправлено что-то не то. Прошу проверить работоспособность на предоставленных мной тестовых примерах.

Спасибо за терпение.

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Вт 01 июл 2014 16:28

Указанный вами скрипт не выполнялся в SDAC версии 5.10.0.8

Код: Выделить всё

MSScript.SQL.Text := 'SELECT (10'#10#13 +
  '/'#10#13 +
  '2) AS RES';
MSScript.Execute;
В версии SDAC версии 5.10.0.8 корректно работал скрипт, указанный вами в паралельном топике, или следующий скрипт:

Код: Выделить всё

MSScript.SQL.Text := 'SELECT (10'#10#13 +
  ' / '#10#13 +
  '2) AS RES';
MSScript.Execute;
Мы не можем изменить поведение, когда единственный символ '/' идет в начале строки, так как это повлечет изменения для множества наших пользователей.

Также, пожалуйста, уточните, у вас продолжает происходит access violation во время открытия датасета или происходит какая-то другая ошибка?

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Ср 02 июл 2014 06:16

По-прежнему ситуация такая же, как и в первом посте:
saupg писал(а): ...
Для этого запроса я получаю такое исключение: "Exception class EOLEDBError with message 'Ошибка протокола в потоке TDS'.".

Любое небольшое изменение текста запроса может как изменить поведение процесса, так и исправить ошибку.
Например, если в последней строке запроса изменить "LEFT JOIN" на "FULL JOIN", то получаем ошибку: "Exception class EOLEDBError with message 'Соединение больше нельзя использовать, так как ответ сервера на выполнявшуюся ранее инструкцию имел неправильный формат.'".

А если там же использовать "INNER JOIN", то ошибки нет. Так же ошибка пропадает при изменении таблицы, например, достаточно из первичного ключа исключить один из столбцов.

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Ср 02 июл 2014 06:41

AndreyZ писал(а):В версии SDAC версии 5.10.0.8 корректно работал скрипт, указанный вами в паралельном топике, или следующий скрипт:

Код: Выделить всё

MSScript.SQL.Text := 'SELECT (10'#10#13 +
  ' / '#10#13 +
  '2) AS RES';
MSScript.Execute;
Мы не можем изменить поведение, когда единственный символ '/' идет в начале строки, так как это повлечет изменения для множества наших пользователей.
Да, в реальных SQL-скриптах, как правило, используются отступы в коде. И получается перед "/" несколько пробелов. В таком случае, конечно, всё работает.
Не думал, что в этом будет какая-то принципиальная разница. Спасибо за уточнение.

AndreyZ
Devart Team
Сообщения: 328
Зарегистрирован: Чт 08 сен 2011 13:18

Re: TMSQuery.Open портит память

Сообщение AndreyZ » Чт 03 июл 2014 14:52

saupg писал(а):По-прежнему ситуация такая же, как и в первом посте:
Мы отвечали на этот вопрос: http://forums.devart.com/viewtopic.php?t=29501#p101103
Т.е. для этого случая, чтобы вернуть старое поведение новой версии SDAC необходимо установить опцию TMSQuery.Options.UniqueRecords в False.

А для случая, который вы описали тут http://forums.devart.com/ru/viewtopic.php?t=13809#p4303 мы ответили
AndreyZ писал(а):Для того, чтобы поведение новой версии SDAC было аналогично старой версии TMSQuery.Options.UniqueRecords необходимо установить в True. При этом проблема при выполнении запроса из первого поста будет воспроизводиться как в старой, так и в новой версии. Проверьте, воспроизводится ли у вас проблема при выполнении запроса из первого поста на старой версии SDAC.

saupg
Сообщения: 18
Зарегистрирован: Вт 06 май 2014 07:01

Re: TMSQuery.Open портит память

Сообщение saupg » Пт 04 июл 2014 06:48

AndreyZ писал(а):Мы отвечали на этот вопрос: http://forums.devart.com/viewtopic.php?t=29501#p101103
Т.е. для этого случая, чтобы вернуть старое поведение новой версии SDAC необходимо установить опцию TMSQuery.Options.UniqueRecords в False.
Я оценил это как временное решение, а не постоянный костыль :(
К тому же были проблемы с UniqueRecords = False и вызовом метода Prepare.
Сейчас проблема с Prepare вроде бы отсутствует.

И тогда такой вопрос. Раз вы знаете в чём проблема, расскажите, пожалуйста, по каким критериям можно определить _не_возможность использования режима UniqueRecords = True?
В своей программе я наткнулся пока только на один проблемный запрос, но запросов в программе тысячи, есть довольно редко используемые и со сложными кейсами, на тестирование которых уйдёт уйма времени.
Или проще сразу всем Query проставить UniqueRecords = False, и в дальнейшей разработке для новых Query отключать этот режим? Зачем он тогда нужен по умолчанию, если его работа так непредсказуема?

Закрыто