воскресенье, 10 апреля 2016 г.

Caché: Прямой доступ через CacheObject

В качестве одного из основных способов доступа к базе данных Caché InterSystems предлагает COM - библиотеку CacheObject. Это библиотека, реализующая объектный интерфейс на стороне клиента к объекту на стороне сервера. Благодаря тому, что COM - объекты поддерживают динамическое изменение типа, становится технически возможным использование одной библиотеки типов, которая может поддерживать различные классы. Что и было реализовано в CacheObject.

Создав объект класса CacheObject.Factory, мы можем в соединенном с базой данных состоянии создать экземпляр любого объекта Caché. При этом клиентская сторона не нуждается в переписывании и пересборке при изменении серверной части.


Полная документация на CacheObject может быть получена в составе дистрибутива СУБД Caché, поэтому подробно описывать функциональность этой библиотеки не будем. Рассмотрим ее применение в нестандартной задаче - а именно, не по назначению.

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

В качестве компромисса, поскольку CacheObject не может обращаться к серверу иначе как через объект, создадим объект, который будет играть роль транспортного компонента. С помощью библиотеки CacheObject будем вызывать этот объект, а он будет выполнять уже что нужно. Демонстрацию будем проводить в силу простоты на клиенте, выполненном на Perl в реализации ActiveState с использованием пакета Win32::OLE. Приведенные действия выполнялись с использованием ActivePerl 5.6 и Cache for Windows NT (Intel) 4.1.3. Приведенные примеры по идее должны также работать и в других версиях.

Создадим класс, например User.DCO (Direct CacheObject). Класс наследуем от класса %Library.RegisteredObject, поскольку объект этого класса должен будет находиться только в памяти и сохранять его в базе данных мы не предполагаем. Впрочем, в реальных условиях это может оказаться не так.

Скелетом скрипта проверки выберем такой:
#!/usr/bin/perl
use Win32::OLE;
$connect = Win32::OLE->new( 'CacheObject.Factory');
$connect->Connect( 'cn_iptcp:127.0.0.1[1972]:USER');

$obj = $connect->Static( 'User.DCO');

# do with class methods of User.DCO class

undef $obj;
undef $connect;
Здесь на языке Perl написано, что используется пакет Win32::OLE, что создается переменная $connect, являющаяся OLE объектом класса CacheObject.Factory, что далее создается переменная $obj, являющаяся переходником на методы класса CacheObject.ObjInstance (это описано в документации) и что в конце скрипта вызываются функции undef для $obj и $connect. Для OLE объектов это означает их закрытие. Сначала закрывается транспортный объект, затем объект коннекта к серверу. Приведенный код рассчитан на настройки по умолчанию. В случае использования авторизации следует уточнить по документации точное выражения для строки параметров коннекта.

В нашем случае рассмотрим подробнее класс User.DCO. Для выполнения прямого доступа в Caché нам потребуется функциональность:
  • чтения переменной или результата вызова функции
  • запись переменной
  • выполнение COS - выражения.
  • выполнение COS - выражения с получением результата его вывода в текущее устройство.


Для этих целей выполним 4 метода класса - Read, Write, Execute и ExecuteTerminal. Как увидим позже, все необходимые операции прямого доступа выполнимы через эти 4 метода.

Метод Read должен принять строку, в которой написано, что именно он должен прочитать и вернуть строку со значением этого выражения. Составим метод:
  method Read(What:%CacheString)
  {
    returntype = %Library.CacheString;
    classmethod;
    not final;
    public;
    sqlproc = 0;
    code = 
    {
      : q @What
    }
  }
Здесь использовалась косвенность при получении значения выражения. Для проверки чтения вызовем просто print $obj->Read( '$zv');. При вызове чтения мы просто вызвали функцию Read. При ее выполнении Perl обратился к пакету Win32::OLE, в котором запрос был перенаправлен в библиотеку CacheObject путем вызова IDispatch:Invoke. Библиотека CacheObject, отвечая на запрос, вызвала на сервере соответствующий метод класса. Благодаря тому, что имена функций CacheObject получает в виде строк, мы можем писать сразу $obj->Read() без дополнительных украшательств. Многие инструментальные средства поддерживают такой же механизм и позволяют транслировать код не зная о самом наличии использованных функций, тем более их прототипов, например, Delphi.

Для выполнения записи составим аналогичный метод, но принимающий два аргумента - имя переменной и значение переменной.
  method Write(What:%CacheString,Value:%CacheString)
  {
    classmethod;
    not final;
    public;
    sqlproc = 0;
    code = 
    {
      : x "s "_What_"=Value"
    }
  }
Здесь так же используется косвенность, поскольку имя переменной передается в виде строки. Для проверки используем код:
  $obj->Write('a', '123');
  print $obj->Read( 'a');


Для выполнения произвольного выражения нужно передать на сервер строку с требуемым выражением и косвенно выполнить его:
  method Execute(What:%CacheString)
  {
    classmethod;
    not final;
    public;
    sqlproc = 0;
    code = 
    {
      : x What
    }
  }
Для проверки выполним:
  $obj->Execute('s a=123456');
  print $obj->Read( 'a');


И, самое сложное - это выполнить код, который выводит что-то в текущее устройство с помощью команды write. Поверьте, такого кода существует немало, причем заменить его на что-то другое практически невозможно. Это код, ориентированный на терминальный доступ. Для того, чтобы получить результат работы такой программы, используем спулер.

При использовании спулера сначала сохраним текущее устройство, создадим устройство спулера, переключимся на него, выполним косвенно выражение, после чего восстановим устройство.
  method ExecuteTerminal(What:%CacheString)
  {
    returntype = %Library.Integer;
    classmethod;
    not final;
    public;
    sqlproc = 0;
    code = 
    {
      : n io,file,start,last
      : s io=$IO
      : s file=+$J
      : s start=1
      : k ^SPOOL(file)
      : o 2:(file:start)
      : u 2
      : x What
      : u io
      : c 2
      : s last=$O(^SPOOL(file,""),-1)
      : k ^SPOOL(file,last)
      : Quit file
    }

  }


Здесь метод возвращает внутреннее имя файла устройства, которое является числом. Используя это число, мы можем выбрать выведенные в спулер данные, обращаясь к глобали ^SPOOL с помощью примерно такого кода:
  n index s index=""
  f  s index=$o(^SPOOL(file,index)) q:index=""  d
  . w ^SPOOL(file,index)


В глобали в каждой строке уже содержатся символы перевода строки. Для проверки выполним код:
  $file = $obj->ExecuteTerminal( 
    "d CompileList^\%apiOBJ(\"User.DODBC\",\"C\",.tmp)");
  $index = $obj->Read( "\$O(^SPOOL($file,\"\"))");
  while ( $index ne "")
  {
    print $obj->Read( "^SPOOL($file,$index)");
    $index = $obj->Read( "\$O(^SPOOL($file,$index))");
  }
  $obj->Execute( "k ^SPOOL($file)");
Приведенный код выполняет компиляцию класса User.DODBC в спулер, выбирает строки из спулера и удаляет за собой служебные данные.

Таким образом, с помощью библиотеки CacheObject мы получили прямой доступ к СУБД Caché и можем выполнить практически произвольной сложности код.

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

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

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