1 (edited by k245 2017-11-08 13:02:02)

Topic: Скрипты, рекурсия и SQLQuery()

Написал рекурсивный скрипт загрузки древовидных данных главного меню:

procedure LoadMenu(miParent: TMenuItem; idParent: integer);
var
  Results: TDataSet;
  miChild: TMenuItem;
  sForm: string;
  index: integer;
begin
  if idParent = -1 then
    SQLQuery('SELECT id, name, form FROM main_menu WHERE id_parent is null', Results)
  else
    SQLQuery('SELECT id, name, form FROM main_menu WHERE id_parent='+inttostr(idParent), Results);
  while not Results.Eof do
  begin
    miChild := TMenuItem.Create (frmMain.MainMenu);
    miChild.Caption := Results.FieldByName('name').AsString;
    sForm := Results.FieldByName('form').AsString;
    if sForm<>'' then
    begin
      miChild.OnClick := @MenuItemClick;
      index := FormList.IndexOf(sForm);
      if index>=0 then
        miChild.Tag := index
      else
      begin
        ShowMessage('Wrong form name: '+sForm)
      end
    end;
    if idParent = -1 then
      frmMain.MainMenu.Items.Insert(0, miChild)
    else
      miParent.Add(miChild);
    // вызываем рекурсивно
    index := Results.FieldByName('id').AsInteger;
    LoadMenu( miChild, index );
    Results.Next;
  end;
end;

http://f2.s.qip.ru/SwgAVRCc.png

Но при работе выяснилось, что по возвращению из рекурсивного вызова локальная переменная Results уже не содержит набора данных и система (на строке Result.Next - ?) выдаёт ошибку:

http://f1.s.qip.ru/SwgAVRCb.png


В примерах с TTreeView загрузка идёт не сразу, а по частям, по мере раскрытия веток (http://myvisualdatabase.com/forum/viewtopic.php?id=3001)


Имеют ли место быть ограничения по рекурсиям в скриптах или по рекурсии в использовании SQLQuery() в частности?


P.S. Склоняюсь, что дело в FastScript и запрете на рекурсию, так как при изменении процедуры ошибки были самыми разными, от классического AV до весьма экзотических... Пока рекурсию победить не удалось, сделал загрузку главного меню двумя процедурами, для двух уровней.
http://f2.s.qip.ru/SwgAVRCk.png

Визуальное программирование: блог и телеграм-канал.

Re: Скрипты, рекурсия и SQLQuery()

К сожалению с этим не помогу, возможно и правда есть какое либо ограничение в FastScript

Dmitry.

Re: Скрипты, рекурсия и SQLQuery()

я рекурсию как то  использовал в функции (в фастскрипте)
Попробуйте завернуть процедуру как функцию

Re: Скрипты, рекурсия и SQLQuery()

iacovlogica wrote:

я рекурсию как то  использовал в функции (в фастскрипте)
Попробуйте завернуть процедуру как функцию

Попробовал, результат отрицательный:

http://f3.s.qip.ru/SwgAVRCq.png


Как выяснилось, вызывает ошибку не рекурсия (которая, кстати, работает правильно, см. код ниже), а специфика работы SQLQuery()  - предположительно, статическое хранение переменной с результатом. На это косвенно указывает сообщение об ошибке, которое обычно появляется, если скрипт теряет указатель на объект.


Вот полный код функции:

// рекурсия -
function LoadMenu(miParent: TMenuItem; idParent: integer):integer;
var
  Results: TDataSet;
  miChild: TMenuItem;
  sForm: string;
  index: integer;
  i: integer;
begin
  if idParent = -1 then
    SQLQuery('SELECT main_menu.id, main_menu.name, main_menu.form FROM main_menu '+
    'left join menu_roles on menu_roles.id_main_menu = main_menu.id '+
    'WHERE (main_menu.id_parent is null) and (menu_roles.id_roles='+inttostr(RoleID)+') order by orderNum desc', Results)
  else
    SQLQuery('SELECT main_menu.id, main_menu.name, main_menu.form FROM main_menu '+
    'left join menu_roles on menu_roles.id_main_menu = main_menu.id '+
    'WHERE (main_menu.id_parent='+inttostr(idParent)+') and (menu_roles.id_roles='+inttostr(RoleID)+') order by orderNum', Results);
  while not Results.Eof do
  begin
    miChild := TMenuItem.Create (frmMain.MainMenu);
    if idParent = -1 then
      frmMain.MainMenu.Items.Insert(0, miChild)
    else
      miParent.Add(miChild);
    miChild.Caption := Results.FieldByName('name').AsString;
    sForm := Results.FieldByName('form').AsString;
    // к пункту меню привязана либо форма, либо вложенные пункты меню
    if sForm<>'' then
    begin
      miChild.OnClick := @MenuItemClick;
      index := FormList.IndexOf(sForm);
      if index>=0 then
        miChild.Tag := index
      else
      begin
        ShowMessage('Wrong form name: '+sForm)
      end
    end
    else
    begin
      result := LoadMenu( miChild , Results.FieldByName('id').AsInteger );
    end;
    Results.Next;
  end;
end;

После выхода из рекурсивного вызова, объект Results уничтожается, а так как он оказывается глобальным, команда Results.Next не выполняется, а отображается вышеприведённое сообщение об ошибке.


Пример проверки рекурсии:

function load(var A:integer):integer;
var
  i: integer;
begin
  i := A;
  if A<5 then
    result := load(A+1) + i
  else
    result := A;
end;

begin
  ShowMessage('Recursive = '+inttostr(load(1)));
end.

Результат верный:

http://f6.s.qip.ru/SwgAVRCr.png


Если это возможно, прошу проверить реализацию создания объекта с результатом (TDataSet) в функции SQLQuery().

Визуальное программирование: блог и телеграм-канал.

5 (edited by iacovlogica 2017-11-09 08:27:12)

Re: Скрипты, рекурсия и SQLQuery()

.....объект Results уничтожается....

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

Re: Скрипты, рекурсия и SQLQuery()

iacovlogica wrote:

...может попробовать разделить , например один датасет заполнить именами окон а второй всем(по каждому окну в другой процедуре ) ...

Можно избавиться от TDataSet и SQLQuery() в рекурсивной функции, например, сделав функцию-оболочку для SQLQuery(), которая загоняет результат в TStringList, а потом спокойно работать со списком и выковыривать из строк нужные значения...  Но всё же хочется более простых решений smile

Визуальное программирование: блог и телеграм-канал.

Re: Скрипты, рекурсия и SQLQuery()

SQLQuery  Возвращает данные только в DataSet посему без него только функцию SQLExecute разве что можно задействовать для получения  данных из БД .
Пришлите ваш проект мне на почту , только обрежьте все кроме того что касается непосредственно  меню (если хотите)  . Есть мысль  ))))

Re: Скрипты, рекурсия и SQLQuery()

Если гора не идёт к Магомету, то... меняем структуру данных:

http://f2.s.qip.ru/SwgAVRCJ.png

Теперь можно обойтись без рекурсии, пожертвовав удобностью настройки меню. Структура меню определяется строковым полем LEVEL (Уровень). Обычно это поле заполняется на триггерах БД на основе id_parent и order_num, но можно и ручками внести smile

http://f4.s.qip.ru/SwgAVRCL.png

Код получился сравнительно простой, загружает до 10 уровней вложенности (на практике больше трёх уровней вложенности в меню не встречал)

// загрузка за один присест
procedure LoadMenu;
var
  Results: TDataSet;
  // до 10 уровней вложенности меню!
  ParentList: array[0..9] of TMenuItem;
  miChild: TMenuItem;
  sForm: string;
  iCurLevel: integer;
  index: integer;
  s: string;
begin
  S :='SELECT main_menu.name, main_menu.form, main_menu.level FROM main_menu '+
    'left join menu_roles on menu_roles.id_main_menu = main_menu.id '+
    'WHERE (menu_roles.id_roles='+inttostr(RoleID)+') order by level';
  SQLQuery(S,Results);
  while not Results.Eof do
  begin
    sForm := Results.FieldByName('form').AsString;
    iCurLevel := length(Results.FieldByName('level').AsString)-1;
    miChild := TMenuItem.Create (MainMenu);
    if iCurLevel = 0 then
    begin
      MainMenu.Items.Add(miChild);
    end
    else
    begin
      TMenuItem(ParentList[iCurLevel-1]).Add(miChild)
    end;
    ParentList[iCurLevel] := miChild;
    //
    miChild.Caption := Results.FieldByName('name').AsString;
    // к пункту меню привязана либо форма, либо вложенные пункты меню
    if sForm<>'' then
    begin
      miChild.OnClick := @MenuItemClick;
      index := FormList.IndexOf(sForm);
      if index>=0 then
        miChild.Tag := index
      else
      begin
        ShowMessage('Wrong form name: '+sForm)
      end
    end;
    Results.Next;
  end;
end;

Результат радует:

http://f5.s.qip.ru/SwgAVRCK.png

Визуальное программирование: блог и телеграм-канал.

9 (edited by iacovlogica 2017-11-10 10:12:41)

Re: Скрипты, рекурсия и SQLQuery()

Моя мысля была относительно первого вашего алгоритма .
И заключалась в том чтобы :
Глобально объявить массив датасет
var 
       Results : array of Tdataset ;
       IndexRes : int = 0 ;

procedure LoadMenu
   begin
      IndexRes := IndexRes +1;
      SetLength(Results,IndexRes) ;
.....................................
....................................
   SQLQuery('SELECT id, name, form FROM main_menu WHERE id_parent is null', Results[IndexRes-1])
.....................................
......................................
   index := Results[IndexRes-1].FieldByName('id').AsInteger;
    LoadMenu( miChild, index );
......................................
......................................
      IndexRes := IndexRes -1;
      SetLength(Results,IndexRes) ;
   end;

Re: Скрипты, рекурсия и SQLQuery()

iacovlogica wrote:

...Глобально объявить массив датасет...

Интересный вариант, но, скорей всего результат будет таким же smile
Прошлым вечером я поставил кучу экспериментов и в результате выяснил, что любое сочетание SQLQuery() и рекурсивного вызова функций или процедур фатально. Не знаю конкретно, в чём там дело, скорей всего в особенностях работы FastScript.

Визуальное программирование: блог и телеграм-канал.

11 (edited by jonibek 2017-11-11 16:58:06)

Re: Скрипты, рекурсия и SQLQuery()

k245 wrote:

Пример проверки рекурсии:

function load(var A:integer):integer;
var
  i: integer;
begin
  i := A;
  if A<5 then
    result := load(A+1) + i
  else
    result := A;
end;

begin
  ShowMessage('Recursive = '+inttostr(load(1)));
end.

Результат верный:

http://f6.s.qip.ru/SwgAVRCr.png


Если это возможно, прошу проверить реализацию создания объекта с результатом (TDataSet) в функции SQLQuery().

Для чего нужна рекурсия и что даст данный код, объясните чайнику.

12 (edited by iacovlogica 2017-11-11 17:55:03)

Re: Скрипты, рекурсия и SQLQuery()

Данный код считает сумму чисел от 1 до 5    smile .
Про рекурсию можно почитать  например тут http://www.tvd-home.ru/recursion .
Рекурсию без четкого понимания того что делаешь лучше не применять и обойтись циклами , так как можно напороться на проблемы .
В данном случае  недоделанный (имхо) стек в фастскрипте заставил k245 протерять пару дней на поиски проблемы .
Честно говоря само наличие раздела "Реализованные и нереализованные особенности" в букваре к фастскрипту наводит на размышления .

Re: Скрипты, рекурсия и SQLQuery()

jonibek wrote:

Для чего нужна рекурсия и что даст данный код, объясните чайнику.

Рекурсия - это приём в программировании, когда функция или процедура вызывает саму себя.


function load(var A:integer):integer;
var
  i: integer;
begin
  i := A;
  if A<5 then
    result := load(A+1) + i
  else
    result := A;
end;


У меня была гипотеза, что FastScript некорректно работает с рекурсивными функциями или процедурами. Данный код опроверг эту гипотезу, и уважаемый iacovlogica так же подтвердил, что с рекурсией всё в порядке. Никакой другой пользы от данного кода нет smile

А вот использование в рекурсивной процедуре функции SQLQuery() приводит к фатальной ошибке, о чем, собственно и весь этот топик smile

Визуальное программирование: блог и телеграм-канал.