понедельник, 11 апреля 2016 г.

Cache': Как сделать двунаправленный запрос

Мне давно было интересно, можно ли сделать в Cache' такой запрос, чтобы его можно было бы прокручивать назад, например что-то вроде команды, парной к Fetch, например Prior. Собственные средства Cache' почему-то не предоставляют такой возможности. Для этого я изучил характер взаимодействия sql-движка с Cache Object Script. В результате исследований выяснилось, что это возможно, хотя и не столь гладко, как бы того хотелось. Надеюсь, читатель с пониманием отнесется к возникшей некрасивости.
Возьмем и сделаем рутину со следующим текстом:
run()
 &sql(declare cur CURSOR for select ID, Name, Home 
   from Sample.Person order by ID asc)
 &sql(open cur)
 &sql(fetch cur)
 &sql(close cur)
 q
Скомпилируем и сохраним текст полученной int-рутины. После чего изменим рутину следующим образом:
run()
 &sql(declare cur CURSOR for select ID, Name, Home 
   from Sample.Person order by ID desc)
 &sql(open cur)
 &sql(fetch cur)
 &sql(close cur)
 q
И также сохраним текст полученной int-рутины. В запросе можно использовать любую имеющуюся у Вас таблицу, просто в данном случае я использовал таблицу из штатного дистрибутива.

Сличим полученные тексты int-рутин. Ничего особенно романтичного в работе автоматического генератора не наблюдается, за исключением того, что сгенерированные тексты полностью совпадают за исключением замены операции $o() на $zp(), которые друг другу прямо противоположны по направлению. Таким образом, для реализации двунаправленной прокрутки используем оба варианта и попробуем совместить их данные, оставив и использовав коды (рутины) доступа.

Для работы нам потребуются некие дежурные данные. Создаем новый класс, например User.NameList, наследник %Persistent и %Populate. Добавляем ему новое свойство Name:%String. Сохраняем, компилируем. В терминале создаем 10 объектов для теста:
d ##class(User.NameList).Populate()
Запускаем SQL Manager и для проверки что действительно создана таблица sql и содержит тестовые данные, выполняем запрос
select ID, Name from NameList.
Если все было в порядке, то будет показана табличка с двумя колонками и десятком строк. Имена англоязычные, вымышленные. Для проверки работы прокрутки в обе стороны создадим рутину (например FetchBack) с кодом
Test()
 n ascHandle,descHandle,ascSelect,descSelect,
 n ok,i,AtEnd,Row,ID,Name,State,ascClose,descClose
 ; первоначальное выражение - "select ID, Name from NameList"
 ; чтобы ходить по нему, преобразуем в два
 s ascSelect="select ID, Name from NameList order by ID ASC"
 s descSelect="select ID, Name from NameList order by ID DESC"

 S ok=##class(%DynamicQuery).SQLPrepare(.descHandle,descSelect)
 S ok=##class(%DynamicQuery).SQLExecute(.descHandle)
 s descClose=descHandle

 S ok=##class(%DynamicQuery).SQLPrepare(.ascHandle,ascSelect)
 S ok=##class(%DynamicQuery).SQLExecute(.ascHandle)
 s State=$li(ascHandle,1)
 s ascClose=ascHandle

 ; идем на 4 шага вперед
 s $li(ascHandle,1)=State
 w "4 steps forward",!
 f i=1:1:4 d
 . d ##class(%DynamicQuery).SQLFetch(.ascHandle,.Row,.AtEnd)
 . q:(AtEnd=1)!(Row="")
 . s ID=$li(Row,1)
 . s Name=$li(Row,2)
 . w "ID="_ID_", Name="_Name,!
 s State=$li(ascHandle)

 ; возвращаемся на 2 шага назад
 s $li(descHandle,1)=State
 w "2 steps backward",!
 f i=1:1:2 d
 . d ##class(%DynamicQuery).SQLFetch(.descHandle,.Row,.AtEnd)
 . q:(AtEnd=1)!(Row="")
 . s ID=$li(Row,1)
 . s Name=$li(Row,2)
 . w "ID="_ID_", Name="_Name,!
 s State=$li(descHandle)

 ; снова на 4 шага вперед
 s $li(ascHandle,1)=State
 w "4 steps forward",!
 f i=1:1:4 d
 . d ##class(%DynamicQuery).SQLFetch(.ascHandle,.Row,.AtEnd)
 . q:(AtEnd=1)!(Row="")
 . s ID=$li(Row,1)
 . s Name=$li(Row,2)
 . w "ID="_ID_", Name="_Name,!
 s State=$li(ascHandle)

 d ##class(%DynamicQuery).SQLClose(.ascClose)
 d ##class(%DynamicQuery).SQLClose(.descClose)
 q
Компилируем, выполняем. В моем случае это выглядит как
USER>d Test^FetchBack()
4 steps forward
ID=1, Name=Presley,Samantha H.
ID=2, Name=Quine,Keith G.
ID=3, Name=Jones,Elvira A.
ID=4, Name=Townsend,Howard D.
2 steps backward
ID=3, Name=Jones,Elvira A.
ID=2, Name=Quine,Keith G.
4 steps forward
ID=3, Name=Jones,Elvira A.
ID=4, Name=Townsend,Howard D.
ID=5, Name=Uberoth,Juanita D.
ID=6, Name=Van De Griek,Michael K.
Видим, что два использованных запроса гладко сшились по используемому состоянию. Это обусловлено тем, что оба запроса совершенно идентичны за исключением направления сортировки. Исследование кодов возврата функций позволяет сделать суждение о более правильном отношении к закрытию запросов.

Таким образом, составить новый класс или набор классов, аналогичные по интерфейсу штатным, не составляет особых проблем. За исключением того, что имея один запрос мы должны будем сделать из него два. Для этого введем некоторую некрасивость - в теле запроса потребуем указания обеих сортировок и разделителей, ограничивающих собственно запрос и сортировки. Выберем в качестве разделителя символы $$$ и будем полагать, что запрос строится по схеме:
select ... 
from ... 
where ... 
$$$ order by ... asc 
$$$ order by ... desc
И что при этом программист возьмет на себя интеллектуальную работу по повторению сортировки в обоих концах. Поля, попадающие в сортировку, должны действительно определять строку выборки. Если поля в выборке допускают неуникальные строки, то следует ввести поле, введение которого сделает строку уникальной. Это требуется для корректного сшивания сортировок. Впрочем, это достаточно поверхностное теоретическое суждение и после его проверки оно может оказаться неверным в каких-то версиях.

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

Внимание привлекает поведение функций доступа, например, класс %DynamicSQLQuery, функция Fetch:
n %qref,rtn Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
Quit:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
QUIT $$Fetch^@rtn
Как видно по тексту, сама функция принимает и передает qHandle по ссылке, но к сгенерированной рутине обращается, передавая значения через локальные переменные. Код сгенерированной рутины закрыт, но судя по тому, что состояние qHandle должно быть передано наружу, ее модифицировать может только вызываемая рутина $$Fetch^@rtn. Других проблемных моментов я не нашел, поэтому к делу.

Экспортируем классы %DynamicQuery, %DynamicSQLQuery и $ResultSet в cdl. Дописываем к их именам символ 2 и правим коды так, чтобы у %DynamicSQLQuery2 была внутренняя поддержка обратной прокрутки, более развернутого хендла, чтобы у класса %DynamicQuery использовался запрос типа %DynamicSQLQuery2 и чтобы класс %ResultSet2 имел еще одну функцию, обращающуюся к FetchBack.

Получившиеся у меня для Cache' 4.1.9 классы можно загрузить по ссылке в конце страницы.

Проверочный код примерно следующего вида:
run()
 n result,i
 Set result=##class(%ResultSet2).%New("%DynamicQuery2:SQL")
 Do result.Prepare("select ID, Name from NameList 
   $$$ order by ID ASC $$$ order by ID DESC")
 Do result.Execute()
 f i=1:1:4 d
 . d result.Next()
 . Write result.Get("ID"),result.Get("Name"),!
 f i=1:1:2 d
 . d result.Prior()
 . Write result.Get("ID"),result.Get("Name"),!
 f i=1:1:4 d
 . d result.Next()
 . Write result.Get("ID"),result.Get("Name"),!
 Do result.%Close()
 q
В результате его работы в терминале выдаются примерно следующие данные:
USER>d run^fetch()
1Presley,Samantha H.
2Quine,Keith G.
3Jones,Elvira A.
4Townsend,Howard D.
3Jones,Elvira A.
2Quine,Keith G.
3Jones,Elvira A.
4Townsend,Howard D.
5Uberoth,Juanita D.
6Van De Griek,Michael K.
Поскольку данные сгенерированы случайным образом, в Вашем случае они могут выглядеть иначе.

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


В версии Cache 5 появился дополнительный класс %ScrollableResultSet, который примерно то же самое и делает, и кроме того объект этого класса может быть сохранен в базе данных. Это позволяет просто организовать подкачку страниц например для веб интерфейса - выдачу строк порциями с сохранением контекста запроса между обращениями к базе например.

class %Library.DynamicQuery2
{

 abstract;
 system;


 query SQL(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16)
 {
  type = %DynamicSQLQuery2; 
 }

}

class %Library.DynamicSQLQuery2
{

 abstract = 0;
 datatype;
 ddlallowed = 0;
 description = "The <b>%DynamicSQLQuery</b> class is used for defining class queries based on runtime SQL statements.";
 not final;
 sqlrowidprivate = 0;
 super = %Query;
 system;

 include
 {
  code = %occInclude;
  generator = %occInclude;
 }

 method Close(Handle:%Binary)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  code =
  [
   n %qref,rtn,ret,qHandle
         ; Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,2)
         s qHandle=$lb(%qref,rtn)
   q:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
   s ret=$$Close^@rtn
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,3)
         s qHandle=$lb(%qref,rtn)
   s ret=$$Close^@rtn
         q ret
  ]
 }

 method Execute(&Handle:%Binary)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  generator =
  [
   s %code=0
   #; don't generate any code if it not for a query
   i %mode="method" QUIT $$$OK
   
   n %sc,formal,i,line,qformal,qline,query
   
   #; Reset the formal list to the query declaration:
   s formal=$g($$$METHformalspec($$$pCOM,$$$CQ(%class,%property),%method))
   s qformal=$g($$$QUERYformalspec($$$pCOM,%class,%property))
   i $e(qformal)="(",$e(qformal,$l(qformal))=")"
   i  s qformal=$e(qformal,2,$l(qformal)-1)
   
   s $$$METHformalspec($$$pCOM,$$$CQ(%class,%property),%method)=formal_$s(qformal'=""&(formal'=""):",",1:"")_qformal
   s %sc=$$SetOneQueryMeth^%occQuery(%class,%property,%method) q:$$$ISERR(%sc) %sc
   
         $$$GENERATE(" n %qref,rtn,ret,qHandle")
         $$$GENERATE(" s %qref=$li(Handle,1)")
         $$$GENERATE(" s rtn=$li(Handle,2)")
         $$$GENERATE(" s qHandle=$lb(%qref,rtn)")
         $$$GENERATE(" Quit:%qref=""""!(rtn="""") $$$ERROR($$$GeneralError,""Query not Prepared"")")
         $$$GENERATE(" s ret=$$Execute^@rtn")
         $$$GENERATE(" s %qref=$li(qHandle,1)")
         $$$GENERATE(" s $li(Handle,1)=%qref")
         $$$GENERATE(" q ret")

   ; $$$GENERATE(" n %qref,rtn Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)")
   ; $$$GENERATE(" Quit:%qref=""""!(rtn="""") $$$ERROR($$$GeneralError,""Query not Prepared"")")
   ; $$$GENERATE(" Quit $$Execute^@rtn")
   QUIT $$$OK
  ]
 }

 method FetchBack(&Handle:%Binary,&Row:%List,&AtEnd:%Integer=0)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  code =
  [
   n %qref,rtn,qHandle,ret
         ; Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,3)
         s qHandle=$lb(%qref,rtn)
   Quit:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
   s ret=$$Fetch^@rtn
         s %qref=$li(qHandle,1)
         s $li(Handle,1)=%qref
         q ret
  ]
 }

 method Fetch(&Handle:%Binary,&Row:%List,&AtEnd:%Integer=0)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  code =
  [
   n %qref,rtn,qHandle,ret
         ; Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,2)
         s qHandle=$lb(%qref,rtn)
   Quit:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
   s ret=$$Fetch^@rtn
         s %qref=$li(qHandle,1)
         s $li(Handle,1)=%qref
         q ret
  ]
 }

 method FetchRows(&Handle:%Binary,FetchCount:%Integer=0,&RowSet:%List,&ReturnCount:%Integer,&AtEnd:%Integer)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  code =
  [
   n %qref,rtn,qHandle,ret
         ; Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,2)
         s qHandle=$lb(%qref,rtn)
   Quit:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
   s ret=$$FetchRows^@rtn
         s %qref=$li(qHandle,1)
         s $li(Handle,1)=%qref
         q ret
  ]
 }

 method GetInfo(&colinfo:%List,&parminfo:%List,&idinfo:%List,&Handle:%Binary)
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  code =
  [
   n %qref,rtn,qHandle,ret
         ; Set %qref=$lg(qHandle,1),rtn=$lg(qHandle,2)
         s %qref=$li(Handle,1)
         s rtn=$li(Handle,2)
         s qHandle=$lb(%qref,rtn)
   Quit:%qref=""!(rtn="") $$$ERROR($$$GeneralError,"Query not Prepared")
   s ret=$$GetInfo^@rtn
         s %qref=$lg(qHandle,1)
         s $li(Handle,1)=%qref
         q ret
  ]
 }

 method Prepare(&qHandle:%Binary,sqltext:%String="",containid:%Integer=0,selectmode:%String="RUNTIME")
 {
  returntype = %Status;
  classmethod;
  not final;
  public;
  sqlproc = 0;
  generator =
  [
   s %code=0
   i %mode="method" QUIT $$$OK
   n formallist,qformal
   s qformal=$g($$$QUERYformalspec($$$pCOM,%class,%property))
   i $e(qformal)="(",$e(qformal,$l(qformal))=")"
   i  s qformal=$e(qformal,2,$l(qformal)-1)
   s formallist=""
   i qformal'="" s formallist=$$formallist^%occName(qformal,"") s formallist=$e(formallist,2,$l(formallist)-1)
   $$$GENERATE(" n asctext,desctext,ret,aschandle,deschandle")
         $$$GENERATE(" s asctext=$p(sqltext,""$$$"",1)_"" ""_$p(sqltext,""$$$"",2)")
         $$$GENERATE(" s desctext=$p(sqltext,""$$$"",1)_"" ""_$p(sqltext,""$$$"",3)")
         $$$GENERATE(" s ret=$$Prepare^%ourQuery(.aschandle,asctext,containid,selectmode,"""_formallist_""")")
         $$$GENERATE(" s ret=$$Prepare^%ourQuery(.deschandle,desctext,containid,selectmode,"""_formallist_""")")
         $$$GENERATE(" s qHandle=$lb($li(aschandle,1),$li(aschandle,2),$li(deschandle,2))")
         $$$GENERATE(" q ret")
         
         ; $$$GENERATE(" Quit $$Prepare^%ourQuery(.qHandle,sqltext,containid,selectmode,"""_formallist_""")")
   Quit $$$OK
  ]
 }

}

class %Library.ResultSet2
{

 description =
 {
  The <class>%ResultSet</class> class provides a way to use the results 
  class queries from within a <i>Cach&eacute ObjectScript</i> application. It is 
  similar in operation to the <b>ResultSet</b> objects provides with the ActiveX and 
  Java bindings.
  <p>You can use a <class>%ResultSet</class> object as follows:
  <example>
  ; Display the results of the Person class' ByName query to the console.
  Set result=##class(%ResultSet).%New("Person:ByName")
  Do result.Execute("S")
  For  Quit:'result.Next()  Do
  . Write result.Get("Name"),!
  Do result.%Close()
  </example>
  <p>Note you can bind a <class>%ResultSet</class> object to a query by either 
  a) setting the <property>ClassName</property> and <property>QueryName</property> 
  properties or b) passing a string containing the class name and query name (separated 
  by a <b>:</b>) to the <nobr><b>%New</b></nobr> method:
  <example>
  Set result=##class(%ResultSet).%New("Person:ByName")
  </example>
  <h2>Dynamic SQL</h2>
  You can use the <class>%ResultSet</class> class to execute dynamic SQL queries 
  using the system-provided <nobr><var>%DynamicQuery:SQL</var></nobr> query. In this case, use the 
  <method>Prepare</method> method to supply the text of the query. For example:
  
  <example>
  Set result=##class(%ResultSet).%New("%DynamicQuery:SQL")
  
  Do result.Prepare("SELECT ID, Name, Salary FROM Employee WHERE Salary > ?")
  Do result.Execute(10000)
  For  Quit:'result.Next()  Do
  . Write result.Get("Name"),result.Get("Salary"),!
  
  Do result.%Close()
  </example>
  
  Dynamic SQL queries are cached in the same query cache as used by Caché ODBC and JDBC. 
  This means that repeated calls to the same dynamic SQL query do not incur any additional 
  query preparation and optimization overhead. You can view and manage this cache using the 
  <i>Caché SQL Manager</i>.
 }
 super = %RegisteredObject;
 system;

 include
 {
  code = %occInclude;
 }

 attribute AtEnd { initialexpression = 0; private; }
 attribute ClassName { type = %CacheString; description = "The name of the class containing the query to run."; }
 attribute ColIndex { multidimensional; private; }
 attribute ColInfo { private; }
 attribute HasInfo { initialexpression = 0; private; }
 attribute IdInfo { private; }
 attribute IsOpened { initialexpression = 0; private; }
 attribute ParamInfo { private; }
 attribute QHandle { private; }
 attribute QueryName { type = %CacheString; description = "The name of the query to run."; }
 attribute Row { private; }
 attribute RuntimeMode { type = %String; description =
 {
  Use this method to set the SQL runtime mode for the query to be
  executed.  Setting the runtime mode for this ResultSet does not
  permanently change the $zu(115,5) value.  Possible values mode are:
  <ul>
  <li> 0 for LOGICAL mode.
  <li> 1 for ODBC mode.
  <li> 2 for DISPLAY mode.
  <li> "" to use the process wide $zu(115,5) value.
  </ul>
 } initialexpression = ""; }

 method %OnClose()
 {
  returntype = %Status;
  private;
  code =
  [
   i ..IsOpened QUIT ..Close()
   QUIT $$$OK
  ]
 }

 method %OnNew(initvalue:%String)
 {
  returntype = %Status;
  private;
  code =
  [
   new class
   i $g(initvalue)="" QUIT $$$OK
   s initvalue=$tr(initvalue,",",":")
   i initvalue[":"
   i  s class=$p(initvalue,":",1),..QueryName=$p(initvalue,":",2)
   e  n len s len=$l(initvalue,"."),class=$p(initvalue,".",1,len-1),..QueryName=$p(initvalue,".",len)
   s ..ClassName=class
   QUIT $$$OK
  ]
 }

 method ClassNameSet(class:%String)
 {
  returntype = %Status;
  public;
  code =
  [
   Set i%ClassName=$$$NormalizeClassname(class)
   Quit $$$OK
  ]
 }

 method Close()
 {
  returntype = %Status;
  description = "Closes the current result set cursor.";
  code =
  [
   i '..IsOpened QUIT $$$OK
   n %sc,QHandle
   s QHandle=..QHandle
   #if $$$HasSystemLevelSupport
   s %sc=$zobjclassmethod(..ClassName,..QueryName_"Close",.QHandle)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"Close",.QHandle)
   #endif
   s ..QHandle=QHandle
   s ..Row=""
   s ..AtEnd=0
   s ..IsOpened=0
   QUIT %sc
  ]
 }

 method ContainsId()
 {
  returntype = %Integer;
  description =
  {
   Returns true (1) if the current query contains an object Id. 
   Otherwise it returns false (0).
  }
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT 0
   QUIT $li(..IdInfo,1)
  ]
 }

 method Execute(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16)
 {
  returntype = %Status;
  description =
  {
   Executes the current query.
   <p>The arguments <var>p1...</var> supply the value of any parameters the 
   query may have.
  }
  code =
  [
   n %sc,QHandle,PrevMode
   #if $$$HasSystemLevelSupport
   n pcount s pcount=$zu(141)-1 ; subtract one for %this
   #endif
   i ..ClassName="" QUIT $$$ERROR($$$ClassNameRequired)
   i ..QueryName="" QUIT $$$ERROR($$$QueryNameRequired)
   i '..QueryIsValid() QUIT $$$ERROR($$$QueryDoesNotExist,..ClassName_":"_..QueryName)
   i ..IsOpened QUIT $$$ERROR($$$QueryNotClosed)
   s QHandle=..QHandle
   if ..RuntimeMode'="" set PrevMode=$zu(115,5,..RuntimeMode)
   #if $$$HasSystemLevelSupport
   s %sc=$$InvokeMethodSetArgs^%occRun(..ClassName,"",..QueryName_"Execute",pcount+1,.QHandle,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"Execute",.QHandle,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #endif
   if ..RuntimeMode'="" do $zu(115,5,PrevMode)
   i $$$ISERR(%sc) QUIT %sc
   s ..QHandle=QHandle
   s ..AtEnd=0
   s ..IsOpened=1
   QUIT %sc
  ]
 }

 method Get(name:%String)
 {
  returntype = %String;
  description =
  {
   Returns the value of the column with the name <var>name</var> in the current row of the result set.
   <p>If <var>name</var> is not a valid column name, this method returns an empty string.
  }
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT ""
   QUIT:name'="" $lg(..Row,$G(i%ColIndex(name)))
   QUIT ""
  ]
 }

 method GetColumnCount()
 {
  returntype = %Integer;
  description = "Returns the number of columns in the result set.";
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT 0
   QUIT $ll(..ColInfo)
  ]
 }

 method GetColumnHeader(n:%Integer)
 {
  returntype = %String;
  description = "Returns the column header for column <var>n</var> in the result set.";
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT ""
   n header s header=$lg($li(..ColInfo,n),3)
   i header="" s header=$li($li(..ColInfo,n),1)
   q header
   
  ]
 }

 method GetColumnName(n:%Integer)
 {
  returntype = %String;
  description = "Returns the name of column <var>n</var> in the result set.";
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT ""
   QUIT $li($li(..ColInfo,n),1)
  ]
 }

 method GetData(n:%Integer)
 {
  returntype = %String;
  description = "Returns the value of column <var>n</var> in the current row of the result set.";
  code =
  [
   QUIT $lg(..Row,n)
  ]
 }

 method GetDataByName(name:%String)
 {
  returntype = %String;
  description =
  {
   Returns the value of the column with the name <var>name</var> in the current row of the result set.
   <p>If <var>name</var> is not a valid column name, this method returns an empty string.
   <p>Note: this method has been superceded by the equivalent <method>Get</method> method.
  }
  expression = ..Get(name);
 }

 method GetInfo()
 {
  returntype = %Status;
  private;
  code =
  [
   n %sc,colinfo,paraminfo,idinfo,QHandle
   s QHandle=..QHandle
   #if $$$HasSystemLevelSupport
   s %sc=$zobjclassmethod(..ClassName,..QueryName_"GetInfo",.colinfo,.paraminfo,.idinfo,.QHandle)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"GetInfo",.colinfo,.paraminfo,.idinfo,.QHandle)
   #endif
   i $$$ISERR(%sc) QUIT %sc
   s ..QHandle=$g(QHandle)
   s ..ColInfo=colinfo,..ParamInfo=paraminfo,..IdInfo=idinfo,..HasInfo=1
   k i%ColIndex
   n i f i=1:1:$ll(colinfo) s i%ColIndex($li($li(colinfo,i),1))=i
   QUIT %sc
  ]
 }

 method GetParamCount()
 {
  returntype = %Integer;
  description = "Returns the number of input parameters for the current query.";
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT 0
   QUIT $ll(..ParamInfo)
  ]
 }

 method GetParamName(n:%Integer)
 {
  returntype = %String;
  description = "Returns the name of input parameter <var>n</var> for the current query.";
  code =
  [
   n %sc i '..HasInfo s %sc=..GetInfo() i $$$ISERR(%sc) QUIT ""
   QUIT $li($li(..ParamInfo,n),1)
  ]
 }

 method Next(&%sc:%Status)
 {
  returntype = %Integer;
  description =
  {
   Advance the result set cursor to the next row. Returns 0 if the cursor is at the end of the 
   result set.
  }
  code =
  [
   i '..IsOpened QUIT 0
   i ..AtEnd QUIT 0
   n QHandle,Row,AtEnd,PrevMode
   s QHandle=..QHandle
   if ..RuntimeMode'="" set PrevMode=$zu(115,5,..RuntimeMode)
   #if $$$HasSystemLevelSupport
   s %sc=$zobjclassmethod(..ClassName,..QueryName_"Fetch",.QHandle,.Row,.AtEnd)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"Fetch",.QHandle,.Row,.AtEnd)
   #endif
   if ..RuntimeMode'="" do $zu(115,5,PrevMode)
   i $$$ISERR(%sc) QUIT 0
   s ..QHandle=QHandle
   s ..Row=Row
   s ..AtEnd=AtEnd
   QUIT Row'=""
  ]
 }

 method Prior(&%sc:%Status)
 {
  returntype = %Integer;
  description =
  {
   Advance the result set cursor to the proir row. Returns 0 if the cursor is at the begin of the 
   result set.
  }
  code =
  [
   i '..IsOpened QUIT 0
   i ..AtEnd QUIT 0
   n QHandle,Row,AtEnd,PrevMode
   s QHandle=..QHandle
   if ..RuntimeMode'="" set PrevMode=$zu(115,5,..RuntimeMode)
   #if $$$HasSystemLevelSupport
   s %sc=$zobjclassmethod(..ClassName,..QueryName_"FetchBack",.QHandle,.Row,.AtEnd)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"FetchBack",.QHandle,.Row,.AtEnd)
   #endif
   if ..RuntimeMode'="" do $zu(115,5,PrevMode)
   i $$$ISERR(%sc) QUIT 0
   s ..QHandle=QHandle
   s ..Row=Row
   s ..AtEnd=AtEnd
   QUIT Row'=""
  ]
 }

 method Prepare(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16)
 {
  returntype = %Status;
  description =
  {
   Use this method with <b>dynamic</b> queries to provide the query to be 
   executed. In the case of the <nobr><var>%DynamicQuery:SQL</var></nobr> query, <var>p1</var> 
   is a string containing an SQL query. The query may contain parameters represented 
   by <b>?</b> characters within the query. The values of any parameters are 
   supplied via the <method>Execute</method> method. For example:
   <example>
   Set result=##class(%ResultSet).%New("%DynamicQuery:SQL")
   
   Do result.Prepare("SELECT Name,City FROM Person WHERE Name %STARTSWITH ? AND City = ?")
   
   Do result.Execute("A","Boston")
   For  Quit:'result.Next()  Do
   . Write result.Get("Name"),result.Get("City"),!
   
   Do result.%Close()
   </example>
  }
  code =
  [
   n %sc,QHandle
   #if $$$HasSystemLevelSupport
   n pcount s pcount=$zu(141)-1 ; subtract one for %this
   #endif
   i ..ClassName="" QUIT $$$ERROR($$$ClassNameRequired)
   i ..QueryName="" QUIT $$$ERROR($$$QueryNameRequired)
   i '..QueryIsValid() QUIT $$$ERROR($$$QueryDoesNotExist,..ClassName_":"_..QueryName)
   i ..IsOpened QUIT $$$ERROR($$$QueryNotClosed)
   s QHandle=..QHandle
   #if $$$HasSystemLevelSupport
   s %sc=$$InvokeMethodSetArgs^%occRun(..ClassName,"",..QueryName_"Prepare",pcount+1,.QHandle,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #else
   s %sc=$$InvokeMethod^%occRun(..ClassName,,..QueryName_"Prepare",.QHandle,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #endif
   i $$$ISERR(%sc) QUIT %sc
   s ..QHandle=QHandle
   s ..HasInfo=0
   QUIT %sc
  ]
 }

 method QueryIsValid()
 {
  returntype = %Integer;
  description =
  {
   Returns true (1) if the ClassName and QueryName properties of this 
   <nobr><b>%ResultSet</b></nobr> object refer to a valid class query. 
   Otherwise it returns false (0).
  }
  code =
  [
   i ..ClassName="" QUIT 0
   i ..QueryName="" QUIT 0
   QUIT ''$d($$$QUERY($$$pMAC,..ClassName,..QueryName))
  ]
 }

 method RunQuery(ClassName:%String,QueryName:%String,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16)
 {
  classmethod;
  description = "This is a diagnostic function; it runs the specified query and prints the output to the console.";
  code =
  [
   n rs,columns,%sc,i
   
   #if $$$HasSystemLevelSupport
   n pcount s pcount=$zu(141)-2
   #endif
   
   i $g(ClassName)="" r !,"Class >",ClassName i ClassName="" q
   i $g(QueryName)="" r !,"Query >",QueryName i QueryName="" q
   
   s rs=##class(%ResultSet).%New(ClassName_":"_QueryName)
   
   #if $$$HasSystemLevelSupport
   s %sc=$$InvokeMethodSetArgs^%occRun("",rs,"Execute",pcount,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #else
   s %sc=$$InvokeMethod^%occRun(,rs,"Execute",.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #endif
   
   i $$$ISERR(%sc) d rs.%Close() d DisplayError^%apiOBJ(%sc) q
   
   s columns=rs.GetColumnCount()
   w ! f i=1:1:columns w rs.GetColumnHeader(i),":"
   f  q:rs.Next()=0  w ! f i=1:1:columns w rs.GetData(i),":"
   
   d rs.%Close()
   q
  ]
 }

 method Test(ClassName,QueryName,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16)
 {
  classmethod;
  description = "This method is deprecated; use <b>RunQuery</b>.";
  code =
  [
   #if $$$HasSystemLevelSupport
   n pcount s pcount=$zu(141)
   do InvokeMethodSetArgs^%occRun("%ResultSet",,"RunQuery",pcount,.ClassName,.QueryName,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #else
   do InvokeMethod^%occRun("%ResultSet",,"RunQuery",.ClassName,.QueryName,.p1,.p2,.p3,.p4,.p5,.p6,.p7,.p8,.p9,.p10,.p11,.p12,.p13,.p14,.p15,.p16)
   #endif
   
   QUIT
  ]
 }

}

Комментариев нет:

Отправить комментарий