www.gbsoft.ru

  • Увеличить размер
  • Размер по умолчанию
  • Уменьшить размер

Существует технология, позволяющая работать из Веб-приложений напрямую с БД, используя бизнес-объекты в формате XML

Взаимодействие с объектами GB через XML

 Установка

  1. Установить GBUDF.dll версии 3.112 или выше
  2. Приобрести и установить лицензию на использование формата XML

Основные принципы

Все обращения к базе данных производятся путем вызова хранимых процедур, каждая из которых имеет один входной параметр и один выходной параметр.
Входной и выходной параметры имеют тип VARCHAR(32640) 
Входные и выходные данные передаются в формате XML, кодировка UTF-8 
Если размер входного XML превышает 32640 байт, необходимо «нарезать» его на части нужного размера, и вызвать несколько раз процедуру prepare_param. Последняя часть передается в качестве параметра нужной процедуре.

Пример на PHP:
  $param = $xml->asXML();

  while (strlen($param) > 32640) {
    $slice = substr($param,0, 32640);
    $param = substr($param, 32640); 
    $query = ibase_query('select * from prepare_param(?)', $slice);
    ibase_fetch_row ($query);
  }
Размер выходного XML также может превышать 32640, поэтому нужно конкатенировать получаемые из процедуры строки:

 Пример на PHP:  

  $queryres = '';

  $query = ibase_query('select * from '.$proc.'(?)', $param);
  while ($row = ibase_fetch_row ($query)) 
    $queryres = $queryres.$row[0];
Если в процессе работы процедуры возникает ошибка, то возвращается пакет из 2 полей ErrorCode и ErrorParams и одной записи, содержащей код и параметры ошибки 

Схема XML

XML представляют собой пакеты данных табличной структуры. Каждый пакет содержит набор общих параметров, набор полей и набор записей, в каждой из которых содержатся значения полей. 
XML для обмена данных должен иметь кодировку UTF-8. Некоторые имена полей (в частности, хиповых) являются регистрозависимыми, поэтому рекомендуется соблюдать регистр как в именах тэгов, так и в значениях атрибутов. 
XML для обмена данными имеет следующую структуру: 
<?xml encoding=”utf-8” ?>
<DataPacket>
  <MetaData>
    <Params>
      Общие параметры
    </Params>
    <Fields>
      Поля
    </Fields>
  </MetaData>
  <RowData>
    Записи
  </RowData>
</DataPacket>

Общие параметры

Общие параметры – глобальные параметры пакета данных.
Описание каждого параметра имеет вид: 
<Param Name=”Имя Value=”Значение />

Используются следующие общие параметры:

Эти параметры являются обязательными:
USERID – код пользователя ГБ, возвращается при успешном логине.
ATTACHMENTкод подключения, возвращается при успешном логине.

Эти параметры являются необязательными:

CURRPROC – имя вызываемой процедуры. Нужен для корректного отображения в инструментах мониторинга. Крайне желательно, чтобы он был заполнен

DEPOT – код удаленного отдела. Его заполнение требуется в особых случаях:

  • Когда нужно выполнить какие-либо действия в обход контроля доступа
  • Когда нужно создать объект, код которого имеет заданное значение, в обход генераторов.
  • Когда нужно, чтобы сделанные изменения не ушли по обмену в другие базы
Прежде чем заполнять параметр DEPOT, рекомендуется обратиться к разработчикам!

LOGNAMEимя процедуры, если хочется чтобы информация о запуске и завершении процедуры была показана в GBLOG
Рекомендуется заполнять этот параметр при вызове отчетов

Поля

Поля описывают структуры таблицы, содержащейся в пакете данных.
Описание каждого поля имеет вид:
<Field FieldName=”Имя FieldType=”Тип” >
  Параметры поля
</Field>
Имя поля должно удовлетворять стандартам xml на именование тэгов и атрибутов, поскольку оно же будет использоваться в теге Row
Тип поля может иметь одно из значений:
  • I4 –целое
  • I2 – короткое целое
  • Sn – строка длиной n (например, S20). n не может превышать 250
  • F8 – число с плавающей точкой
  • D8 – дата
  • B – блоб (двоичные данные)
Каждый из параметров поля имеет вид

<Param Name=”Имя Value=”Значение />

Параметры используется только в выходных данных, во входном XML их можно не заполнять.

Используются следующие параметры полей:

Nature – внутренний тип поля.
Возможные значения:
1 – в поле хранится количество (например, товара, в штуках)
2+256*n – в поле хранится сумма в валюте с кодом n
3 – в поле хранятся проценты
4 – в поле хранится дата
6+256*n – в поле хранится имя, максимальная длина n символов
7+256*n – в поле хранится код объекта с типом n
2+256*n – в поле хранится цена товара в валюте с кодом n
10 – в поле хранится субсет
11- в поле хранится хип
12 – в поле хранится логическое значение
14 – в поле хранится путь к файлу
15+256*n – в поле хранится число, которое должно быть отформатировано с n знаками после запятой, и по которому не предполагается вычисление итогов
16+256*n – в поле хранится число, которое должно быть отформатировано с n знаками после запятой, и по которому предполагается вычисление итогов

Attrs – атрибуты поля, битовая маска, используется только в ГБ, можно игнорировать.

Label – подпись к полю

Записи

Каждая запись имеет вид: 
<Row RowState=СостояниеПоле=Значение>
  Субсеты
</Row>
Атрибуты тэга Row содержат значения для каждого простого поля. Каждое поле должно быть описано в MetaData/Fields

Исключение составляет необязательный атрибут RowState, который описывает состояние записи при манипуляциях с объектами – его описывать не нужно.

Значения должны быть отформатированы следующим образом:
Числа с плавающей точкой должны быть записаны без разделителя групп разрядов, с десятичным разделителем «точка»
Даты должны быть записаны в формате DD.MM.YYYY
В строках переводы строк должны быть заменены на ‘<BR>’

Субсеты делятся на 2 типа:
  • Хип – ровно одна запись из любого числа полей строкового типа, каждое поле может иметь любое имя без ограничений. Поле, содержащее в себе хип, как правило, имеет имя Heap
Формат представления хипа:
<Поле>
  <Field FieldName=”Имя Value=”Значение” /></Поле>
  • Субсет – вложенная таблица
Формат представления субсета:
<Поле>
  <DataPacket>
     Описание вложенной таблицы
  </DataPacket>
</Поле>

Основные методы работы

Запуск процедуры

 Последовательность действий:

Подготовить XML с входными данными
Установить общие параметры
При необходимости порезать полученный XML на части и вызвать нужное число раз процедуру prepare_param
Вызвать нужную процедуру и профетчить из нее результаты, сконкатенировав результирующую строку
Пропарсить полученный XML. Проверить, не содержит ли он описание ошибки. Если содержит – достать описание ошибки и запустить ее обработку.

Пример на PHP с использованием SimpleXML :
class PacketError extends Exception { }

function execute_procedure($proc, $xml) {
  global $userid, $attachment;

  if (!isset($xml->MetaData->Params))
    $xml->MetaData->addChild('Params');

  if (count($xml->xpath("MetaData/Params/Param[@Name='USERID']")) == 0) {
    $user = $xml->MetaData->Params->addChild('Param');
    $user->addAttribute('Name','USERID');
    $user->addAttribute('Value',$userid);
  }

  if (count($xml->xpath("MetaData/Params/Param[@Name='ATTACHMENT']")) == 0) {
    $user = $xml->MetaData->Params->addChild('Param');
    $user->addAttribute('Name','ATTACHMENT');
    $user->addAttribute('Value',$attachment);
  }

  $param = $xml->asXML();

  while (strlen($param) > 32640) {
    $slice = substr($param,0,32640);
    $param = substr($param,32640); 
    $query = ibase_query('select * from prepare_param(?)', $slice);
    ibase_fetch_row ($query);
  }

  $queryres = '';

  $query = ibase_query('select * from '.$proc.'(?)', $param);
  while ($row = ibase_fetch_row ($query)) 
    $queryres = $queryres.$row[0];

  ibase_free_result($query); 

  $res = simplexml_load_string($queryres);
  if (isset($res->RowData) && isset($res->RowData->Row) && isset($res->RowData->Row['ErrorCode']))
    throw new PacketError($res->RowData->Row['ErrorCode'].':'.$res->RowData->Row['ErrorParams']);
  return $res;
}

Вызов процедуры с входным XML, содержащим однострочную таблицу без субсетов

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

Пример на PHP с использованием SimpleXML :

function packet_get($proc, $args, $values) {
  // create params xml
  $xml = simplexml_load_string('<?xml version="1.0" encoding="utf-8"?><DataPacket><MetaData><Fields></Fields></MetaData><RowData/></DataPacket>');
  foreach ($args as $fieldname => $fieldtype) {
    $field = $xml->MetaData->Fields->addChild('Field');
    $field->addAttribute('FieldName', $fieldname);
    $field->addAttribute('FieldType', $fieldtype);
  }

  // add row, fill values
  $row = $xml->RowData->addChild('Row');
  for ($i = 0; $i < count($values); $i++) {
    $fieldname = $xml->MetaData->Fields->Field[$i]['FieldName'];
    $row->addAttribute($fieldname, $values[$i]);
  }

  return execute_procedure($proc, $xml);
}

Логин

Выполнить процедуру security_connect_packet, в качестве входных параметров однострочная таблица, поле username типа строка содержит имя пользователя, поле computername типа строка содержит имя или IP-адрес хоста, с которого производится вход, поле login (целое) содержит 1.
Проверить, что вход успешен (не произошло ошибки и в выходной таблице LoginResult=1).
Запомнить возвращенный код пользователя (в выходных данных, поле User) и код подключения (в общих параметрах, параметр ATTACHMENT) для последующих вызовов

Пример на PHP с использованием SimpleXML :
function gb_login($user) {
  global $userid, $attachment;
  try {
    $xml = packet_get('security_connect_packet',array('username'=>'S200','computername'=>'S200','login'=>'I4'),array($user,'www',1));
  } catch(PacketError $e) {
    return false;
  }

  if (isset($xml->RowData) && isset($xml->RowData->Row)) {
    $res = $xml->RowData->Row['LoginResult'];
    if ($res == 1) {
      $userid = $xml->RowData->Row['User'];
      $attachment_param = $xml->xpath("MetaData/Params/Param[@Name='ATTACHMENT']");
      if (count($attachment_param) == 1)
        $attachment = $attachment_param[0]['Value'];
      return true;
    }
    else
      return false;
  }
  else
    return false;  
}

Логофф

Необходимо выполнять в конце работы

Выполнить процедуру security_connect_packet, в качестве входных параметров однострочная таблица, поле login (целое) содержит 0.
Обнулить запомненные коды пользователя и подключения

Пример на PHP с использованием SimpleXML :
function gb_logoff($user) {
  global $userid, $attachment;
  try {
    $xml = packet_get('security_connect_packet',array('username'=>'S200','computername'=>'S200','login'=>'I4'),array($user,'www',0));
  } catch(PacketError $e) {
    return false;
  }

  $userid = 0;
  $attachment = 0;

  return true;
}

Получение свойств объекта 

Получение свойств одного или нескольких объектов заданного типа производится путем вызова процедуры XXX_GET, где XXX – название типа, которое определяется по таблице типов http://wiki.gbsoft.ru/wiki/Типы_объектов_ГБ

 Способ вызова процедур стандартный – на входе задается XML с 3 полями

Type, тип I4 – код типа объекта (из таблицы)
Ids, тип B – список кодов объектов через точку с запятой. Максимальная длина списка 8192 байт, если нужно получить свойтва для большего числа объектов, нужно вызывать процедуру несколько раз
Props, тип B – список требуемых свойств объектов, через точку с запятой, в начале и в конце списка тоже точка с запятой. Например, если требуется получить название объекта, в поле Props нужно передать ;Name;
Если нужно получить все свойства объекта, можно передать в Props значение *

 На выходе процедура вернет XML, содержащий запрошенные поля и по одной записи на каждый запрошенный объект.

 Перечень стандартных свойств объектов приведен в приложении 1.

Очевидно, что можно реализовать универсальный метод GB_GET, который по типу объекта, массиву кодов и массиву свойств вернет таблицу с нужными кодами и свойствами.

Пример на PHP громоздкий и непрозрачный, поэтому здесь не приведен

Работа с отчетами

Названия и типы полей для параметров отчета можно узнать из описания, а при его отсутствии – из текста хранимой процедуры, либо запустив отчет из gb с ключом –packetlog или –packetlogfile. В последнем случае при запуске отчета в ГБ будет выведено в лог название процедуры, а также имена и типы всех полей, содержащих параметры отчета.

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

Рекомендуемый способ работы: реализовать метод AddIndirectFields, который получает на входе полученный XML и список вычисляемых полей в формате Articul.Name;Articul.Heap.@Name
Для каждого поля в списке ищется поле данных (Articul), определяется его внутренний тип (Param с именем Nature), в нашем случае это товары, значит значение параметра будет 7+256=263. по формуле (Nature>>8)&256 определяем код типа – в нашем случае 1. По таблице определяем название типа – Articuls, следовательно нужно вызвать процедуру Articuls_Get. Проанализировав весь список полей, получаем необходимый набор свойств - ;Name;Heap;
Пробежав по всем записям, определяем список кодов товаров. Затем нужное число раз зовем Articuls_Get

 В идеале реализация процедуры AddIndirectFields должна поддерживать рекурсию, чтобы допускать задание полей в виде Articul.Node.Name или даже Partner.City.Name – здесь нужно сперва достать регионы из партнеров, а затем названия регионов.

Кроме того, в ГБ часто используются выражения вида Articul.Heap.@Name для получения значений хиповых полей, и Articul.Heap.@Supplier[fmId+otPartners].Name – для приведения типов хиповых полей на лету

Пример на PHP громоздкий и непрозрачный, поэтому здесь не приведен

Изменение объектов

Для создания, изменения и удаления объектов используются процедуры XXX_PUT

На входе эти процедуры получают данные той же структуры, что возвращает XXX_GET, с информацией о том, что изменилось

Для создания объекта нужно:
Вызвать XXX_GET с Ids=-1 и Props=*
Будет получен XML с одной записью, в которой все поля пусты, а субсеты содержат набор полей.
Нужно установить RowState записи = 4 и заполнить поля. Если при создании добавляются записи в субсеты, то нужно для каждой записи субсета также установить RowState=4

Например, нужно создать документ. Для этого сперва вызываем Documents_Get. Получаем поля документа, а также поля товарных строк документа в субсете Goodies.

Заполняем поля документа, добавляем записи в субсет Goodies, вызываем Documents_Put

Для удаления объекта с заданным кодом нужно:
Вызвать XXX_GET с Ids=код и Props=*
Будет получен XML с одной записью. Нужно установить в ней RowState=2 и вызвать XXX_PUT

Для изменения заданных полей объекта с заданным кодом нужно:
Вызвать XXX_GET с Ids=код и Props=*
Будет получен XML с одной записью. Нужно установить в этой записи RowState = 1, затем создать новую запись (тэг Row) с RowState = 8 и заполнить в нем только изменяемые поля.
Если изменяется субсет, то субсет в записи с RowState=8 должен содержать только изменения, по тем же правилам: вставка записи – RowState=4, удаление записи – RowState=2, изменение – пара записей, оригинальная с RowState=1 и измененные поля c RowState=8

За один вызов процедуры можно вставить, изменить или удалить любое количество объектов.
 

Последняя версия ГБ

Поддержка

c 9-00 до 19-00

т. (383) 226-84-10, 220-96-94