ORACLE CALL INTERFACE (OCI)

Карпов Владислав Софтсервис

Введение

Structured Query Language (SQL) - не процедурный язык. Программа на не процедурном языке определяет набор операций с данными, но каким образом будет выполнена та или иная операция над этими данными остается привилегией внутренних механизмов системы поддержки не процедурного языка. Не процедурный характер SQL делает его очень легким в изучении и в использовании для запросов к базе данных. Однако, большинство языков, на которых пишутся системы, тесно работающие с базами данных, являются процедурными, более того объектно-ориентированными. Выполнение языковых конструкций сильно зависит от результатов выполнения предыдущих или последующих предикатов. Для этих целей в языках высокого уровня введены специальные конструкции циклов, ветвлений, условий и т.д. Процедурный характер языков высокого уровня делает их более сложными, чем SQL, но вместе с тем более гибкими и более мощными. OCI позволяет Вам разрабатывать прикладные программы, которые используют преимущества не процедурного языка SQL и преимущества процедурного языка высокого уровня. При помощи OCI Вы можете также использовать процедурное расширение SQL - PL/SQL. Таким образом, прикладные программы, которые Вы разрабатываете, могут быть более мощными и гибкими, чем программы, написанные исключительно на PL/SQL или просто SQL. Не секрет, что наиболее мощные программы получаются, когда при их написании используются разные инструменты. Нет инструмента, одинаково хорошо включающего в себя все качества, необходимые для решения той или иной задачи.

Что такое OCI?

OCI - это набор интерфейсных функций, позволяющих манипулировать объектами ORACLE сервера. Библиотека функций, поддерживающих OCI, выполнена в виде Windows DLL библиотеки, и поэтому подходит для любых систем программирования, ориентированных на операционную систему Windows. На вопрос ‘А есть что-либо похожее под DOS?’ ответ однозначен - для версии ORACLE 7.3.3 нет, и быть не может. Так как компания ORACLE объявила что, начиная с версии 7.3, операционная системы DOS поддерживаться не будет. Именно по этой причине Вы не сможете манипулировать ORACLE сервером, допустим, из CLIPPER программ, поскольку CLIPPER программы являются DOS приложениями. Я, наверное, сильно обидел CLIPPER-манов, но это чистая правда.

OCI поддерживает любое SQL определение данных, манипулирование данными, любые запросы по данным, средства управления транзакциями - все, что доступно в ORACLE 7 сервере.

Ко всему прочему OCI позволяет выполнять блоки, написанные на PL/SQL структурированном языке.

Написание программ с использованием OCI

Основная структура программы с использованием OCI.

Когда Вы пишите некое приложение с использованием Oracle Call Intrface (OCI), Вам следует придерживаться схемы, приведенной ниже, для полной уверенности правильной работы Вашего приложения. Как минимум Ваша OCI программа должна выполнить следующие шаги:

Структуры данных OCI приложения.

В OCI программах Вам необходимо зарезервировать память для структур данных, необходимых для соединения с ORACLE серверами выполнения SQL предложений и PL/SQL блоков. Структуры данных, которые Вы будите использовать для соединения с сервером, называются logon data area (LDA) и host data area (HDA). Вы должны объявить одну пару LDA-HDA структур для одного конекта к базе данных ORACLE. Вы передаете ссылки на эти структуры в вызов функции OLOG, которая и производит всю работу по соединению с базой.

Вы можете повторно использовать пару LDA HDA. Конект установленный ранее с этой парой будет прерван (disconnect).

Для того, чтобы выполнить SQL предложения или PL/SQL блок, Вам необходимо объявить курсор (cursor). Курсор объявляется при помощи структуры под названием cursor date area (CDA). Для каждого курсора необходима структура CDA. Курсор объявляется выполнением функции OOPEN, в которую передается ссылка на структуру CDA и на структуру соединения LDA. Таким образом, курсор жестко привязывается к соединению. Технологически объявление курсора представляет собой резервирование памяти на сервере для выполнения SQL предложений и инициализации внутренних установок. В одном соединении Вы можете объявить несколько курсоров для выполнения различных SQL предложений. Вы обязаны закрыть курсор после работы с ним, по крайней мере, это хороший тон программирования, не взирая на то, что сервер сам закрывает курсоры перед Disconnect-ом. После закрытия курсора Вы вправе использовать повторно структуру CDA для открытия другого курсора даже в другом конекте.

Фактически структуры LDA и CDA совершенно одинаковы. И там, и там есть поле под названием return code (rc). Это поле является двоичным 16-битным значением, хранящим код ошибки в случае некорректного завершения OCI вызова, связанного с LDA или CDA либо 0, если вызов прошел успешно. Для словесного описания ошибки можно использовать функции oerhms либо oermsg.

Logon Data Area (LDA)

Logon Data Area (LDA) есть структура, которая ассоциируется с активным конектом к базе данных ORACLE через вызов OLOG. Формат структуры LDA для типичной 32 разрядной системы показана на схеме:

Размер структуры LDA и смещение в байтах полей зависит от платформы (системно-зависимая структура). Однако, все поля присутствуют для всех систем. Для Windows 95 платформа LDA представляет следующее (ocidfn.h):

 

struct cda_def {

sb2 v2_rc; /* V2 код возврата */

ub2 ft; /* SQL тип функции */

ub4 rpc; /* количество строк */

ub2 peo; /* смещение ошибки parse */

ub1 fc; /* код функции OCI */

ub1 rcs1; /* Заполнитель */

ub2 rc; /* V7 код возврата */

ub1 wrn; /* флаг предупреждения */

ub1 rcs2; /* зарезервировано */

sword rcs3; /* зарезервировано */

struct { /* структура rowid */

struct {

ub4 rcs4;

ub2 rcs5;

ub1 rcs6;

} rd;

ub4 rcs7;

ub2 rcs8;

} rid;

sword ose; /* Ошибка операционной системы */

ub1 chk;

dvoid *rcsp; /* Указатель на зар. область */

ub1 rcs9[CDA_SIZE - sizeof (struct cda_head)]; /* Запол. */

};

 

где

 

struct cda_head {

sb2 v2_rc; /* V2 код возврата */

ub2 ft; /* SQL тип функции */

ub4 rpc; /* количество строк */

ub2 peo; /* смещение ошибки parse */

ub1 fc; /* код функции OCI */

ub1 rcs1; /* Заполнитель */

ub2 rc; /* V7 код возврата */

ub1 wrn; /* флаг предупреждения */

ub1 rcs2; /* зарезервировано */

sword rcs3; /* зарезервировано */

struct { /* структура rowid */

struct {

ub4 rcs4;

ub2 rcs5;

ub1 rcs6;

} rd;

ub4 rcs7;

ub2 rcs8;

} rid;

sword ose; /* Ошибка операционной системы */

ub1 chk;

dvoid *rcsp; /* Указатель на зар. область */

};

 

#define CDA_SIZE 64

 

для Windows 95 (OraTypes.h):

 

eb1 char

ub1 unsigned char

sb1 signed char

 

eb2 short

ub2 unsigned short

sb2 signed short

 

eb4 int

ub4 unsigned int

sb4 signed int

 

eword int

uword unsigned int

sword signed int

 

dvoid void

 

В файле ocidfn.h определена структура cda_def, собственно структура lda_def есть просто переопределение нового типа от структуры cda_def:

 

typedef struct cda_def Lda_Def;

 

В общем, наиболее используемое поле структуры Lda_Def есть код возврата (rc). Для OCI программ, взаимодействующих с сервером версии 7.3.3, не используйте поле ‘код возврата V2’ (для второй версии). Это поле оставлено для совместимости OCI процедур со второй версией сервера.

Весьма важное замечание: Области памяти, содержащие LDA и HDA структуры, должны бать статичны . Это значит, что пока существует конект с базой , ни одна структура не может быть перемещена в памяти.

Host Data Area (HDA)

Host Data Area (HDA) - это обычно 256 байт, которые Вы обязаны зарезервировать для каждого конекта с ORACLE сервером. Вы резервируете LDA и HDA структуры в статической памяти для каждого конекта с базой и передаете ссылки на эти структуры в функцию OCI, обеспечивающую связь - OLOG.

Длина HDA 256 байт только для 32 разрядных систем, к таким как Win 95 и Win NT. В 64 битных системах эта область занимает 512 байт. Тем не менее, прежде, чем писать OCI приложения, обязательно загляните в документацию ORACLE для своей системы, чтобы убедиться в размере HDA. Даже в некоторых 32 разрядных системах размер этой области может быть 512 байт. Можно и сразу сделать HDA 512 байт для любой системы - это может повысить мобильность и переносимость Вашей программы.

Весьма важное замечание: HDA должны быть правильно объявлена и инициализирована до того, как она будут использована в OCI приложении. HDA должна быть проинициализирована 0 (двоичным нулем, не символом ‘0’) перед первым вызовом OLOG, иначе возможна ошибка соединения.

Cursor Data Area (CDA)

Cursor Data Area (CDA) - это область памяти, которая обеспечивает отображение информации между приложением клиентом и областью памяти сервера для выполнения SQL выражений. Информация о исполняемом SQL выражении сохраняется в system global area (SGA) и в частной SQL области. Как только ORACLE выполнил SQL выражение, поля CDA автоматически заполняются новой информацией, соответствующей агрегированному результату выполненного выражения. Поля CDA показывают статус и прогресс выполнения SQL выражения. Структура CDA в терминах языка С была уже мной представлена, схематично это выглядит следующим образом:

Полное описание полей структуры не сильно важно для работы. За описанием обращайтесь к ORACLE документации.

Выполнение SQL предложений

При написании программ Вы обязаны всегда помнить о предназначении того или иного SQL выражения. Существует 8типов SQL выражений в ORACLE 7

 Язык определения данных (Data Definition Language DDL)

 Управляющие выражения (3 типа)

 Управление транзакциями (Transaction Control)

 Управление сессиями (Session control)

 Управление системой (System control)

 Язык манипулирования данными (Data manipulation Language DML)

 PL/SQL

 Запросы (Queries)

 Внедряемый SQL (Embedded SQL)

Запросы представляют дополнительный тип выражений при использовании OCI. Часто запросы (Queries) классифицируют как DML, но в OCI программах это нечто другое.

 

Рассмотрим более подробно все типы SQL выражений:

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

 

CREATE TABLE wine_list (name CHAR(20), type CHAR(20), year NUMBER(4),bin NUMBER(4))

DROP TABLE wine_list

GRANT UPDATE, INSERT, DELETE ON wine_list TO scott

REVOKE UPDATE ON wine_list FROM scott

 

OCI приложения могут поддерживать Управление транзакциями, Управление Сессиями и Управление системой. Для более подробной информации обратитесь к документации ORACLE 7 Server SQL Reference

Язык манипулирования данными (DML) служит для изменения содержимого таблиц базы данных. Выражения DML могут включать:

  • INSERT – для включения новых строк в таблицы
  • UPDATE – для модификации существующих строк в таблице
  • DELETE – для удаления строк из таблиц
  • LOCK - блокировка таблиц для других пользователей
  • EXPLAIN - описание плана выполнения SQL выражения

DML выражения при использовании их в OCI программе могут иметь параметры. Параметры можно связать с переменными памяти Вашей программы.

PL/SQL - есть процедурное расширение языка ORACLE SQL. Процедуры, написанные на PL/SQL, обладают значительно большей мощностью, чем одиночные предложения SQL. PL/SQL позволяет выполнить сразу несколько SQL предложений одним блоком. В PL/SQL входят следующие конструкции:

  • Одно или более SQL выражений
  • Определение переменных
  • Операции присвоения
  • Управляющие конструкции языка, такие как IF-THEN-ELSE, и циклы
  • Обработка исключительных ситуаций

При использовании PL/SQL блоков в своих приложениях Вы также можете:

  • Вызывать хранимые на сервере процедуры и функции
  • Комбинировать процедурные управляющие конструкции с SQL предложениями для выполнения всего этого в одном блоке.
  • Вы получаете доступ к специальным возможностям языка PL/SQL, таким как - записям, таблицам, CURSORS FOR loops, и обработкой исключительных ситуаций.
  • Использование переменных Курсоров.

Запросы – это SQL выражения, которые выбирают данные из базы данных. Запрос можем не возвратить ничего, может возвратить только одну строку, а может очень много строк. Все запросы начинаются с SQL выражения SELECT. Запросы часто квалифицируют как DML подмножество языка SQL, но в контексте OCI приложений они являются особой субстанцией SQL.

Внедряемый SQL описывается обычно в терминах препроцессора процедурного языка высокого уровня. Препроцессор переводит внедряемый SQL в вызовы OCI функций.

Шаги выполнения SQL предложений

При написании OCI кода для выполнения SQL предложений необходимо предпринять несколько различных шагов, причем, количество шагов в каждом случае свое. Для операций типа PieceWise (извлечение, модификация и заполнение данных в поля типа LONG и LONG RAW по частям) требуется столько шагов, сколько необходимо для достижения конца данных (данные могут достигать размера до 2 MB).

После конекта с базой данных и открытия курсора основные шаги по выполнению SQL выражений следующие:

  1. Сделать разбор SQL выражения, используя вызов OPARSE. Функция OPARSE не передает никаких связанных значений в SQL выражение и не получает результат выполнения SQL выражения. Однако SQL выражение может быть сразу выполнено на сервере в том случае, если OCI приложение работает в не отложенном режиме (non-deferred) или в случае установки параметра функции OPARSE DEFFLG в не 0. Никакой дальнейшей обработки SQL выражения не требуется. RALLBACK или COMMIT можно выполнить сразу после OPARSE.
  2. Для DML выражений и запросов, если туда надо передавать параметры, необходимо воспользоваться функциями OBNDRA, OBNDRV, OBNDRN или OBINDPS для связывания адресов переменных памяти с параметрами с SQL выражением. В качестве адресов переменных могут быть использованы адреса начальных элементов массивов. Наиболее мощная функция среди всех перечисленных - OBINDPS. Она работает только в отложенном режиме выполнения, но позволяет связывать с SQL выражением массивы структур и поддерживает операции типа PieceWise.
  3. Для запросов необходимо описать структуру выходного множества. Этим занимается функция ODESC. Собственно этот шаг не обязателен, если Вы хорошо себе представляете, что должно получиться в результате выполнения SQL выражения уже на стадии компиляции. Во всех остальных случаях ODESC необходима, в частности, если Вы хотите написать, например, общий класс выполнения любых SQL выражений. При попытке описать выходную выборку для не запросов ODESC выдает ошибку выполнения.
  4. Для запросов каждую колонку выходной выборки необходимо связать с адресом переменной, в которую она будет выводиться. Это делается при помощи функций ODEFIN или ODEFINPS. Отметьте, пожалуйста, тот факт, что Вы не можете использовать ODEFIN или ODEFINPS для определения выходных переменных в анонимных PL/SQL блоках, вместо этого должны быть использованы функции OBNDRV или OBNDRA. Функция ODEFINPS может быть использована только в отложенном режиме работы и при необходимости выполнения PieceWise операций и связывания выходной выборки с массивом структур.
  5. Для DML выражений и выражений управления транзакциями используйте OEXN для выполнения SQL выражения. Если разбор SQL выражения был отложенным, то выполнение происходит именно на этом шаге.
  6. Для запросов вызываете OEXFET или комбинацию OEXEC и OFETCH. Это приводит к исполнению запроса и извлечению строк, удовлетворяющих запросу. Если не все строки извлечены из результирующей выборки после вызова OEXFET, вызываете OFEN до полного перебора всей выборки.

В завершении работы приложение должно закрыть курсор исделать дисконект от базы данных ORACLE.

Выполнение отложенных SQL предложений

До выхода в свет ORACLE 7 сервер (Release 7.0) каждый вызов OCI процедуры требовал пересылки ее на сервер с последующей обработкой ее там. При разборе SQL предложения при помощи вызова OSQL3 текст SQL выражения транспортируется на сервер, лексически разбирается и сохраняется в Глобальной Памяти Процессов (process global area (PGA)). Адреса входных переменных пересылаются в сервер для идентификации с шаблонами входных переменных (выражения типа Par1, Par2 и т.д.), находящихся в SQL выражении, причем, на каждую переменную требуется один вызов сервера. В дополнение ко всему для запросов требуется один вызов сервера для связывания одной колонки результирующей выборки с одной выходной переменной. В заключении, при выполнении SQL выражения, ORACLE требует от приложения клиента реальное значение для каждой входной переменной и для запросов возвращает результат. Все это приводит к дополнительным вызовам сервера.

Когда OCI приложение и ORACLE сервер работают на одной машине (Personal ORACLE), множество вызовов сервера приводят к незначительным задержкам при выполнении программы. В сети же все значительно печальней (клиент и сервер могут быть разнесены на тысячи километров друг от друга). В этом случае временные задержки могут быть очень серьезными.

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

Разработка OCI приложений

Этот раздел подробно описывает следующие шаги при разработке OCI приложений:

Определение структур данных для OCI приложения (LDA, HDA, CDA)

До присоединения к ORACLE серверу Ваше приложение должно определить хотя бы одну переменную со структурой LDA. Если в Вашем приложении будет использовано одновременно несколько соединений (может быть даже с различными серверами) Вам следует определить одно LDA для каждого соединения. Определение LDA зависит от языка высокого уровня, на котором Вы пишите OCI приложение. Вы так же обязаны определить HDA для каждого LDA. Одна пара LDA/HDA будет использована для одного конекта.

Для выполнения SQL предложений Вам потребуется, по крайней мере, одна структура CDA, для хранения информации об открытом курсоре. Если Вы одновременно собираетесь выполнять несколько SQL выражений, то по одному CDA на каждый открытый курсор.

Соединение с сервером

Соединение с одним или несколькими серверами в Ваше программе осуществляется через вызов функции OLOG. В качестве параметров используются ссылки на структуры LDA и HDA. Одному соединению соответствует одна пара LDA/HDA, передаваемая в функцию OLOG. Таким образом, один вызов OLOG соответствует одному соединению. Повторный вызов OLOG приводит к разъединению уже существующего конекта на паре LDA/HDA и новому соединению.

Ограничения соединений с сервером

В однозадачных приложениях одновременно Вы можете работать только с одной базой данных. Вы можете сделать несколько конектов к одной и той же базе, но имейте в виду, что ORACLE считает соединения, кроме того, - это неразумное растрачивание ресурсов.

Существует два метода соединения к ORACLE серверу: bloking и non-bloking. В режиме blokingпри вызове OCI процедур управление Вашей программе передается только после того, как отработала OCI функция. При этом неважно как она завершила свою работу - корректно или вернула признак ошибки. При non-bloking режиме выполнение OCI процедур происходит в потоках, поэтому управление Вашей программе передается сразу же после вызова OCI функции. В этом случае Ваше приложение может, например, сделать еще запрос на сервер, в момент ожидания ответа от сервера на первый запрос.

Открытие курсора

Для выполнения SQL выражения Вы должны открыть курсор. При открытии курсора Вы устанавливаете связь между структурами данных на сервере, при помощи которых сервер выполняет SQL выражение и CDA структурой в Вашем приложении. Это связывание происходит по вызову функции OOPEN. Открытие курсора требует информации о соединении с сервером. Практически это осуществляется путем передачи в функцию OOPEN ссылки на структуру LDA, хранящую информацию о соединении. Естественно LDA должна побывать перед этим в вызове OLOG.

Каждый открытый курсор в OCI приложении ассоциируется с сервером/базой данных. В случае, если OCI приложение имеет несколько соединений, общее количество курсоров, открытых в одной базе данных не должно превышать параметр OPEN_CURSORS в этой базе данных.

Вы можете использовать курсор для выполнения SQL предложения одного и того же много раз, можете использовать для выполнения различных SQL предложений. Когда курсор используется вторично с другим SQL выражением, параметры CDA структуры автоматически будут обновлены при лексическом разборе нового SQL выражения. Для этого совсем не обязательно закрывать старый и открывать новый курсор. Более того, я Вам сильно не рекомендую этим заниматься.

Лексический разбор SQL предложения

Любое SQL выражение должно быть подвергнуто лексическому разбору на сервере при помощи вызова OCI процедуры OPARSE. Главное и первое, что передается в этот вызов - это ссылка на структуру CDA (открытый курсор) и собственно текст самого SQL выражения, которое требует разбора. Я еще очень подробно поговорю об этой важной функции в дальнейшем, при описании примеров использования OCI.

DDL выражения могут быть выполнены сразу после разбора выражения, и OEXEC не требуется. Это происходит, когда программа собрана в режиме non-deferred (не отложенный) или если Вы указали флаг DEFFLG, равный 0 при вызове OPARSE. В любом другом случае требуется вызов OEXN или OEXEC для исполнения выражения.

Корпорация ORACLE рекомендует использовать отложенный режим выполнения SQL выражений во всех случаях, где это возможно. Это сильно ускоряет выполнение программы, особенно в сетевой среде. Прошу отметить тот факт, что, некоторые ошибки в отложенном варианте могут быть выявлены только на стадии, собственно выполнения, в отличие от не отложенного варианта. Все DML команды, PL/SQL блоки, и запросы требуют дополнительной обработки сразу после лексического разбора и исполнения.

Связывание адресов входных переменных

Большинство DML команд и некоторые запросы (в частности те которые, содержат WHERE предложение) требуют от программы передачи на ORACLE сервер не только самого SQL выражения, но и некоторых входных параметров, требующихся для его выполнения.

Данные в SQL выражения могут входить как литералы. Это можно сделать, когда вы знаете эти данные уже на уровне компиляции. Вы можете, например, написать:

 

INSERT INTO wine_list (name, type, year, bin_no) VALUES(’Joseph Swan Vineyards’, ’ZINFANDEL’, NULL, 112)

 

Как Вы видите, в SQL выражении параметры переданы непосредственно, в качестве литералов: ’ZINFANDEL’, NULL, 112

Литеральный тип передачи параметров очень ограничен. Если Вы захотите поменять параметры, Вы будите вынуждены перекомпилировать программу, либо придумать механизм генерации литеральных SQL в ран тайме, что тоже не слишком эффективно. Для решения этой проблемы ORACLE предусмотрел связывание (bind) адресов входных параметров с шаблонами (placeholders) этих параметров на стадии выполнения.

Placeholders

Когда Вы определяете SQL выражение или PL/SQL блок, которые содержат входные параметры времени выполнения, Вы определяете шаблоны (placeholders) в этих SQL выражениях, которые будут заменены на реальные данные. Пример SQL выражения с шаблонами:

 

INSERT INTO wine_list (name, type, year, bin_no) VALUES(:Name, :Type, :Year, :Bin_Number)

 

Этот INSERT содержит 4 шаблона ввода. Следующий пример PL/SQL блока содержит 2 шаблона:

 

char plsql_statement[] = ” BEGIN\

AISE_SALARY(:EMP_NUMBER, :NEW_SAL);\

END;”;

 

Вы можете использовать шаблоны для любых входных переменных в любых DELETE, INSERT, SELECT, UPDATE выражениях или PL/SQL блоках, в любых позициях в SQL предложении, где Вы можете использовать выражение или литеральную величину.

Весьма важное замечание: Вы не можете использовать шаблоны для ссылок на другие ORACLE объекты, как-то таблицы или колонки.

Для каждого шаблона в SQL выражении или PL/SQL блоке, Вам необходимо вызвать функцию связывания шаблона с реальным адресом памяти Вашего приложения, где будет храниться соответствующий параметр. Связанные адреса параметров используются только на стадии выполнения SQL предложения. ORACLE сам подставляет соответствующие параметры в соответствующих адресах памяти в SQL предложение на стадии выполнения.

Необязательно иметь реальные данные по адресам связывания на момент связывания. При вызове одной из OCI функций связывания (OBNDRV, OBNDRN, OBNDRA, OBINDPS) происходит лишь сопоставление адреса переменной и соответствующего шаблона. При этом ORACLE учитывает тип данных и длину соответствующей переменной. Однако, убедитесь в правильном значении переменной связывания при исполнении SQL выражения или PL/SQL блока.

Связывание также может происходить по начальным элементам массива значений и массива структуры. Это весьма важное дополнение, так как Вы можете одним INSERT добавить в таблицу целый массив значений вместо необходимости организации цикла по этому массиву.

Весьма важное замечание (**): Если Вы только изменили значение переменной связывания, это не значит, что Вы должны опять произвести операцию связывания для нового исполнения SQL выражения. Для многократного повторения одного и того же SQL предложения Вам надо ОДИН раз сделать лексический разбор предложения, ОДИН раз связать шаблоны с адресами переменных путем многократного присвоения переменных связывания и выполнения SQL предложения добиться желаемого результата. Это важно, так как уменьшает общее время выполнения OCI программы.

Функции связывания адресов

Существует четыре функции, при помощи которых Вы можете связать адреса переменных с шаблонами: OBNDRV, OBNDRN, OBNDRA и OBINDPS.

OBNDRV

Когда Вы используете OBNDRV, Вы должны определить в SQL выражении имя шаблона. В примере, описанном выше, параметр ‘:Year’ есть имя шаблона для величины ‘год’.

Имя шаблона для OBNDRA не может быть зарезервированным словом. Для примера следующее SQL предложение не верно, так как ROWID является зарезервированным словом:

SELECT ename FROM emp WHERE rowid = :ROWID

Зарезервированные ORACLE слова смотрите в документации.

OBNDRN

При использовании OBNDRN каждый шаблон должен иметь вид :N, где N литеральное число от 1 до 255. Пример:

SELECT ename, sal FROM emp WHERE (job = :1 AND sal > :2)OR(job != :1 AND sal < :2)

Отметьте, пожалуйста, что в приведенном примере четыре экземпляра шаблонов, но реально связываются только два адреса. Вы будете вызывать два раза OBNDRN для переменной :1 и переменной :2. OBNDRN позволяет использовать индексные переменные (массивы) для итерационного выполнения SQL предложений.

Весьма важное замечание: Вы не можете использовать OBNDRN для связывания переменных в PL/SQL блоках. В этом случае Вы должны использовать OBNDRA или OBNDRV.

OBNDRA

OBNDRA служит для связывания адресов скалярных переменных и МАССИВОВ в Вашей программе и шаблонов в SQL предложении или PL/SQL блоке. OBNDRA почти то же самое что OBNDRV, но в дополнении к функциям OBNDRV имеет дополнительные параметры, которые показывают максимальный размер массива, количество и длину элементов массива.

OBINDPS

OBINDPS включает функциональность OBNDRA и OBNDRN. А также OPCODE параметр сигнализирует, что приложение будет производить операции INSERT и UPDATE инкрементально на стадии выполнения. OBINDPS может быть использована для операции INSERT с массивом структур.

OBINDPS поддерживается только в отложенном режиме работы и работает, только начиная с версии ORACLE 7.3 или более поздней. OBINDPS поддерживает операции типа piecewise. При недостаточном наборе условий работы OBINDPS Вам следует использовать более старые функции. Соответственно массивы структур работать не будут, также как и операции piecewise.

При написании примеров я отдал предпочтение именно этой функции, поэтому подробное описание ее, я еще приведу.

Описание параметров колонок результирующего набора

Если Ваше SQL выражение есть запрос, то в отдельных случаях Вам необходимо получить информацию о колонках результирующего набора. Это справедливо для рантайм запросов, результат работы которых Вы не знаете на стадии выполнения. Например, в интерактивной среде выполнения SQL выражений SQL Plus, можно набрать:

 

SELECT * FROM wine_list;

 

Вы бы могли набрать что угодно другое, факт тот, что программа заранее не знает ничего о результирующем наборе, в частности - сколько колонок содержит таблица или представление wine_list и о параметрах этих колонок.

Однако, Ваша OCI программа может получить эту информацию, еще не выполняя SQL предложение. Получить информацию о колонках можно при помощи функции ODESCR (describe - описать). ODESCR возвращает полное описание n-ой колонки, где n - входной параметр функции ODESCR.

Вы можете использовать полученную информацию для конвертации, отображения или сохранения данных, полученных из этой колонки. Для получения информации обо всех колонках, входящих в результирующий набор, Вам следует использовать вызов ODESCR в цикле. Установите начальное значение переменной цикла в значение 1 и наращивайте его в цикле, определяя параметры n-ой колонки, процесс повторяется до тех пор, пока не будет получена информация об ошибке ‘varible not in select list’ (переменная вне результирующего набора) (ORA-01007) в поле RC структуры CDA. Следующий фрагмент кода на языке С демонстрирует вышесказанное:

 

for (pos = 1; pos <= NPOS; pos++){

cbufl[pos] = sizeof(cbuf[pos]);

if (odescr(&cda, pos, &dbsize[pos], &dbtype[pos],

&cbuf[pos], &cbufl[pos], &dsize[pos],

&prec[pos], &scale[pos], &nullok[pos])){

if (cda.rc == 1007) break;

oci_error();

continue;

};

};

 

Замечание: При отложенном выполнении OPARSE выполняется в момент первого вызова ODESCR.

Выполнение SQL выражения

Если SQL предложение есть DML команда, то Вам необходимо выполнить это предложение. Операция выполнения подставляет реальные значения связанных переменных в SQL и выполняет предложение.

Существует два различных подхода для выполнения SQL предложений. Один из них - это присвоение переменных связи новыми значениями и выполнение функцией OEXEC. Второй путь - это сформировать массив из входных значений и выполнить все за один раз функцией OEXN. (Функцию OEXN можно также использовать для выполнения запросов с массивом, состоящим из одного элемента, в этом случае будет то же самое, что при первом варианте.)

Весьма важное замечание: Читайте весьма важное замечание (**)

Операции с целыми массивами намного уменьшают трафик в сети при сетевом варианте приложения, особенно при участии в процессах INSERT и UPDATE сравнительно больших объемов данных. Для примера, допустим, необходимо в таблицу добавить 10 записей. При использовании функции OEXEC Вам придется вызывать ее в цикле 10 раз. Это 10 обращений к серверу по сети. С другой стороны, можно один раз обратиться к серверу через сеть, вызвав функцию OEXN и передав ей в качестве параметров значение 10 и массив строк, необходимых для добавления в базу данных. Преимущества налицо.

Определение колонок результирующего набора

Для запросов Вам необходимо определить переменные, в которые будут приниматься данные результирующего набора. Для этих целей Вы скорее всего воспользуетесь функциями ODEFIN или ODEFINPS, которые связывают адреса выходных переменных с колонками результирующего набора. Если Вы не знаете параметры результирующего набора, и описывали их при помощи ODESCR, то ODEFINPS лучше всего вызывать сразу после определения колонок.

Замечание: Вы не можете использовать ODEFINPS в PL/SQL блоках. В этом случае необходимо использовать OBNDRA или OBNDPS (или OBNDRV в данном случае)

Вызывать ODEFIN или ODEFINPS можно повторно для переопределения переменных связывание для колонок результирующего набора. При этом совсем необязательно вызывать повторно OPARSE или OXEC. Т.е. в процессе выполнения Вы можете переопределить местоположение выходной информации.

ODEFINPS наиболее функциональная процедура, которая поддерживает массивы структур и операции piecewise. При написании примеров я использовал именно эту функцию и в дальнейшем я о ней поговорю более подробно.

Извлечение строк данных для запросов (Fetch)

После связывания адресов выходных переменных с колонками результирующего набора Вам необходимо извлечь данные из этого набора в связанные адреса. При этом Вам доступны функции:

Закрытие курсора

Перед тем как Ваша программа завершит свою работу, закройте все открытые ранее курсоры функцией OCLOSE. Как только Вы закрываете курсор Ваша CDA структура больше не ассоциируется с ORACLE сервером, и все ресурсы, связанные с этим курсором на сервере освобождаются.

Замечание: Для выполнения нового SQL предложения Вам совсем необязательно закрывать старый курсор и открывать новый. Можно воспользоваться уже открытым. Все, что надо сделать - это лексический разбор нового предложения.

Запомните, что каждому OOPEN должен соответствовать свой OCLOSE. Для пере открытия курсора лучше закрыть старый, используя OCLOSE.

COMMIT или ROLLBACK

Дисконект от сервера ORACLE происходит по вызову функции OLOGOF, которая автоматически делает COMMIT. Однако Вы можете указать явный COMMIT, выполнив процедуру OCOM. Если Вы хотите откатить транзакцию, то для этого есть функция OROL.

Замечание: При отключении Вашего приложения от сервера, каким либо еще способом, кроме OLOGOF (Разрыв сети, например), и если при этом до этого не был вызван OCOM, транзакция откатывается автоматически.

Отсоединение от сервера

Для отсоединения от сервера используйте вызов OLOGOF. OLOGOF должна быть вызвана для каждой структуры LDA, прошедшей через функцию OLOG.

 

Некоторые общие правила написания OCI приложений.

Максимальная длина массивов

Максимальный размер массива при работе с функциями OCI - 32512. Это параметры ITERS и NROWS в вызовах OEXN, OFEN или OEXFET, и они не должны превышать указанной величины.

 

Не блокирующий режим работы

До появления ORACLE 7 Server Release 7.2 соединение между ORACLE и OCI приложением было только в блокирующем режиме. Release 7.2 уже включал новое свойство: не блокирующий (non-blocking) режим работы.

Замечание: Для использования не блокирующего режима с ORACLE 7 Server Release 7.2, Вам необходима версия 7.2 OCI библиотеки и версия 2.2 SQL*Net; для работы с Release 7.3, Вам потребуется версия 7.2 или 7.3 OCI библиотеки и 2.3. SQL*Net. Release 7.3 OCI библиотеки не совместим с Release 7.2 OCI библиотеки.

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

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

Вы также можете указать режим работы при соединении с сервером в функции OLOG. Параметр mode может принимать значения OCI_LM_DEF (режим по умолчанию - блокирующий) и OCI_LM_NBL (не блокирующий режим). Значения этих констант определены в заголовочном файле ocidfn.h .

Безопасность при работе с многопоточностью

При работе с Release 7.3 ORACLE Server OCI библиотека позволяет разработчику использовать OCI в многопоточном приложении. OCI процедуры, начиная с указанного Release, стали реентерабельными, т.е. допускающими более одного вхождения для выполнения одновременно. Это дает полную гарантию сохранности выполнения отдельно взятого потока в Вашем приложении. До Release 7.3 OCI приложение могло выполняться только в одном потоке.

Введение много поточности в приложение накладывает определенные сложности при программировании. Так, чтобы воспользоваться вызовом OCI процедуры для одного из соединений с сервером, нужно дождаться семафора, что этот конект свободен. Организация семафоров ложится на плечи программиста.

Перед тем, как вызывать любые OCI процедуры, Вы должны сообщить OCI окружению в каком варианте выполняется Ваше приложение: однопоточном или многопоточном. Это достигается при инициализации OCI окружения вызовом функции OPINIT, в которую передается один единственный параметр mode. Параметр mode может принимать одно из двух значений, описанных в ocidfn.h :

OCI_ENV_DEF - однопоточное приложение (по умолчанию).

OCI_ENV_TSF - многопоточное приложение.

Предупреждение: В многопоточном приложении, если не было вызова OPINIT(OCI_ENV_TSF), результат выполнения OCI процедур непредсказуем.

В многопоточном приложении для связи с сервером работает только одна функция - OLOG, OLON и ORLON не работают. OLOG - это новая функция для Release 7.3, рекомендуется использовать именно ее для связи с сервером.

Операции типа PieceWise при выполнении INSERT, UPDATE и FETCH

До появления ORACLE 7 Release 7.3 Server OCI приложения были вынуждены резервировать память для операций INSERT и UPDATE. Это приводило к серьезным проблемам при добавлении или модифицировании полей типа LONG или LONG RAW, размер которых мог достигать до 2 Гб.

До Release 7.3 PieceWise операция была возможна при использовании функции OFLNG, которая до сих пор доступна. OFLNG весьма полезна для извлечения информации по частям с определенного смещения.

В Release 7.3 добавились новые, усовершенствованные функции. Очень большая колонка может быть теперь добавлена или извлечена как набор частей небольшого размера. Этим достигается минимизация резервирования памяти в приложении клиента3.

Операция PieceWise Fetch стала более эффективной. В отличие от OFLNG или других операций ( прямого считывания из базы) она стала буферизирована.

Операции PieseWise стали более гибкими. Размер закачиваемого блока определяется в момент выполнения и может быть любым для извлечения различных кусков информации.

Операции типа PieceWise стали доступны для PL/SQL блоков.

Четыре новых функции были добавлены в OCI для поддержки операция типа PieceWise - OBNDPS, ODEFINPS, OGETPI, OSETPI.

Операция типа PieceWise при выполнении INSERT

  1. Соединитесь с сервером (OLOG), откройте курсор (OOPEN), сделайте разбор SQL выражения (OPARSE)
  2. Свяжите шаблоны с адресами памяти в Вашей программе (OBINDPS). На этом шаге Вы должны определить максимальную длину значения в колонке, которая будет вставляться, но не обязательно определять длину одной части для операции PieceWise.Реальный адрес, с которого будет происходить операция, будет возвращен функцией OGETPI.
  3. Сначала выполните SQL, вызвав OEXEC. На этом шаге часть данных будет отправлена на сервер для INSERT. OEXEC завершится с ошибкой выполнения ORA - 03129 (‘the next piece to be inserted is required’-‘требуется следующая часть данных для INSERT’), если не все данные будут занесены в таблицу. При каком -либо другом завершении процесса OEXEC - это будет значить, что произошла какая- то другая ошибка.
  4. Вызовите OGETPI для извлечения информации о требуемом куске для INSERT. Одним из параметров для функции является адрес, в который возвращается значение, показывающее какой требуется кусок (OCI_FIRST_PIECE) - начальный или (OCI_NEXT_PIECE) -следующий. Возможные значения прописаны в ocidef.h.
  5. OCI программа должна зарезервировать буфер для передачи блока данных на сервер, и вызвать OSETPI. В качестве параметра передается адрес буфера, в котором присутствуют данные для INSERT, указатель на целое, содержащее размер буфера, и опять же указатель на целое, хранящее признак, какая часть данных передается на сервер (OCI_FIRST_PIECE, OCI_NEXT_PIECE, OCI_LAST_PIECE).
  6. Вызвать OEXEC опять. Если будет возвращена константа OCI_LAST_PIECE из OGETPI и OEXEC возвратит 0, то INSERT выполнен полностью. Если OEXEC возвратит ORA-03129, то следует вернуться на Step 4. Если OEXEC вернет что- либо еще, то произошла какая -либо другая ошибка и требуется дальнейшая обработка.

Для PieceWise Fetch и PieceWise Update шаги весьма похожи. Для уточнения алгоритмов для Update и Fetch обратитесь к документации ORACLE.

Использование PL/SQL блоков в OCI приложениях.

PL/SQL - есть процедурное расширение языка SQL. Программы, написанные на PL/SQL, более гибкие по сравнению с единичными SQL командами. PL/SQL позволяет объединять SQL команды в блоки и выполнять одним запросом. Кроме того, PL/SQL включает:

Используя PL/SQL блоки, Ваше приложение получает следующие преимущества:

Для более детального ознакомления с PL/SQL читайте ORACLE документацию.

Для обработки PL/SQL блока Вы должны проделать шаги, которые Вам уже известны. Поместить блок в строковую переменную, сделать лексический разбор, перед этим открыв курсор, связать шаблоны с переменными памяти, выполнить блок и т.д. как с единичным SQL предложением.

При связывании переменных памяти с шаблонами в PL/SQL блоке Вы можете использовать функции OBNDRA, OBINDPS или OBNDRV. OBNDRN использовать с PL/SQL блоками нельзя. Пример PL/SQL блока с шаблонами:

 

BEGIN

SELECT ename,sal,comm INTO :emp_name, :salary, :commission WHERE ename = :emp_number;

END;

 

Вы можете в этом случае использовать OBNDRV или OBINDPS для связывания выходных переменных с шаблонами :EMP_NAME, :SALARY и :COMMISSION и входную переменную для шаблона :EMP_NUMBER.

Замечание: Вы не можете использовать ODEFIN или ODEFINPS для определения выходных переменных при использовании в PL/SQL блоках. Вместо этого адреса можно связать функциями OBNDRV или OBINDPS.

Если в PL/SQL блоке возникла необрабатываемая исключительная ситуация, значения в выходные переменные не будут возвращены.

Типы данных.

Когда Вы изменяете или добавляете данные в ORACLE таблицы, а также, когда Вы пытаетесь принимать данные с сервера при выполнении запросов или данные в переменных связывания. ORACLE представляет эти данные в формате, в котором они хранятся на сервере. Он также может преобразовывать эти данные. Среди типов данных, хранимых на сервере, присутствуют такие типы как NUMBER, CHAR, DATE, и RAW.

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

 

SELECT sal FROM emp WHERE empno = :employee_number

 

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

В первом случае ORACLE серверу необходимо указать, что данные при посылке в переменную OCI приложения должны быть переведены в символьную строку или другими словами указать ORACLE, что внешний тип данных есть строка символов, например, VARCHAR2 (код = 1) или CHAR (код = 96) в параметре FTYPE вызова ODEFIN. При этом Вам необходимо в своей OCI программе определить переменную как строку символов и адрес этой строки указать функции ODEFIN в качестве параметра BUF.

Во втором случае, когда Вы захотите получить SAL в виде двоичного числа (floating-point), серверу указывается внешний тип данных как FLOAT (код = 4). Вам также необходимо определить переменную, длинной необходимой для хранения этого формата, и передать адрес этой переменной в ORACLE, вызвав функцию ODEFIN с параметром BUF, указывающим на эту переменную.

ORACLE переводит форматы данных прозрачно от Вас, т.е. выдает дынные в том виде, в котором Вы его об этом попросите, если такое преобразование вообще возможно. Не каждое преобразование возможно и это естественно. Вы не сможете перевести внутренний формат DATE во внешний формат NUMBER - это очевидно. Вы можете внутри своего OCI приложения определить данные в том формате, в котором они хранятся на сервере, в этом случае никакого дополнительного преобразования не требуется. В качестве примера удобнее всего в этом случае использовать формат DATE (код = 12), аналога этого формата нет ни в одном языке программирования, поэтому программисту лучше всего самому позаботиться о преобразовании. Server воспримет этот формат в том виде, в котором он хранится в памяти OCI приложения.

Для контроля над типами данных Вы должны использовать подходящий внешний тип данных для процедур связывания и определения выходных переменных таких, как OBNDRA или ODEFIN. Вы должны сообщить ORACLE Server это входная или выходная переменная, ее внешний тип и длину.

Внутренние типы данных

Итак, мы выяснили, что ORACLE хранит данные в своем внутреннем представлении. Каждый тип данных имеет свой уникальный номер и символьный идентификатор, который определен в файле ocidfn.h. Для того, чтобы узнать тип данных, которые возвращают запрос, Вы должны вызвать функцию ODESR, которая возвращает уникальный номер внутреннего типа данных для требуемой колонки. Это весьма важно для запросов, так как OCI программа должна решить, как конвертировать внутренний формат представления в формат, понимаемый Вашим OCI приложением.

Следующая таблица описывает все внутренние типы данных и их уникальные коды:

Внутренний тип данных

Максимальный размер

Код типа данных

VARCHAR2

2000 bytes

1

NUMBER

21 bytes

2

LONG

2^31 bytes

8

ROWID

6 bytes

11

DATE

7 bytes

12

RAW

255 bytes

23

LONG RAW

2^31 bytes

24

CHAR

255 bytes

96

MLSLABEL

255 bytes

105

 

Внешние типы данных

Существует список внешних типов данных, поддерживаемых сервером. Внешние типы данных тоже имеют уникальный номер. Номер этот показывает ORACLE серверу как преобразовывать типы при связывании переменных и при определении выходных колонок. Например, если Вы хотите преобразовать внутренний тип NUMBER в строку символов, Вы должны указать в вызове функции ODEFIN внешний тип данных VARCHAR2 (код = 1). Аналогично сказанному Вы определяете внутренние и внешние типы данных для связывания входных переменных.

Будьте внимательны при преобразовании типов, Если Вы попытаетесь присвоить полю с внутренним типом данных DATE значение символьной строки ‘My Birthday’, а не ’04.12.69’, то возникнет ошибка времени выполнения.

Внешние типы данных представлены в таблице:

Внешние типы данных

Типы данных языков высокого уровня

Имя

Код

С

CA-Visual Objects

Delphi

VARCHAR2

1

char[n]

DIM [n] AS BYTE

array [0..n-1]of char

NUMBER

2

unsigned char[21]

DIM [21] AS BYTE

array [0..20]of char

8-bit signed INTEGER

3

signed char

BYTE

char

16-bit signed INTEGER

3

signed short

SHORTINT

SMALLINT

32-bit signed INTEGER

3

signed int, signed long

LONG

INTEGER

FLOAT

4

float, double

FLOAT, REAL8

FLOAT

Null terminated STRING

5

char[n+1]

PSZ

Pchar

VARNUM

6

char[22]

DIM [22] AS BYTE

array [0..21] of char

LONG

8

char[n]

DIM [n] AS BYTE

array [0..n-1] of char

VARCHAR

9

char[n+slen]

DIM [n+slen] AS BYTE

array [0..n+slen-1] of char

ROWID

11

char[n]

DIM [n] AS BYTE

array [0..n-1] of char

DATE

12

char[7]

DIM [7] AS BYTE

array [0..6] of char

VARRAW

15

Unsigned char[n+slen]

DIM [n+slen] AS BYTE

array [0..n+slen-1] of char

RAW

23

unsigned char[n]

DIM [n] AS BYTE

array[0..n-1] as char

LONG RAW

24

unsigned char[n]

DIM [n] AS BYTE

array[0..n-1] as char

UNSIGNED INT

68

Unsigned

WORD

WORD

LONG VARCHAR

94

char[n+ilen]

DIM [n+ilen] AS BYTE

array [0..n+ilen-1] of char

LONG VARRAW

95

Unsigned char[n+ilen]

DIM [n+ilen] AS BYTE

array [0..n+ilen-1] of char

CHAR

96

char[n]

DIM [n] AS BYTE

array [0..n-1] of char

CHARZ

97

char[n+1]

PSZ

Pchar

CURSOR VARIABLE

102

struct cda_def

struct cda_def

struct cda_def

MLSLABEL

105

char[n]

DIM [n] AS BYTE

array [0..n-1] of char

 

Конвертация типов данных между внутреннем и внешнем форматами.

Возможные преобразования типов представлены в таблице:

 

Внутренние типы

Внешние типы

1

varchar2

2

number

8

long

11

rowid

12

date

23

raw

24

long raw

96

char

105

mlslabel

1 varchar2

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3)

I/O

I/O(7)

2 number

I/O(4)

I/O

I

       

I/O(4)

 

3 integer

I/O{4}

I/O

I

       

I/O(4)

 

4 float

I/O(4)

I/O

I

       

I/O(4)

 

5 string

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3, 5)

I/O

I/O(7)

6 varnum

I/O(4)

I/O

I

       

I/O(4)

 

7 decimal

I/O(4)

I/O

I

       

I/O(4)

 

8 long

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3, 5)

I/O

I/O(7)

9 varchar

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3, 5)

I/O

I/O(7)

11 rowid

I

 

I

I/O

     

I

 

12 date

I/O

 

I

 

I/O

   

I

 

15 varraw

I/O(6)

 

I(5, 6)

   

I/O

I/O

I/O(6)

 

23 raw

I/O(6)

 

I(5, 6)

   

I/O

I/O

I/O(6)

 

24 long raw

O(6)

 

I(5, 6)

   

I/O

I/O

I/O(6)

 

68 unsigned

I/O(4)

I/O

I

       

I/O(4)

 

94 long varchar

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3, 5)

I/O

I/O(7)

95 long varraw

I/O(6)

 

I(5, 6)

   

I/O

I/O

I/O(6)

 

96 char

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3)

I/O

I/O(7)

97 charz

I/O

I/O

I/O

I/O(1)

I/O(2)

I/O(3)

I(3)

I/O

I/O(7)

105 mlslabel

I/O(8)

 

I/O(8)

       

I/O(8)

I/O

                   
                   
                   
                   
  1. Для входных переменных строка должны быть в формате ORACLE ‘BBBBBBBB.RRRR.FFFF’. Для выходных- значение колонок возвращается в том же формате
  2. Для входных переменных строка должна быть в стандартном ORACLE формате хранения дат. Для выходных, значение колонок возвращается в том же формате
  3. Для входных переменных строка должна быть в 16 формате. Для выходных, значение колонок возвращается в том же формате
  4. Для выходных переменных колонка должна содержать правильное число
  5. Длина не должна превышать 2000
  6. Для входных переменных значение сохраняется в 16 формате. Для выходных значение колонки должно быть в 16 формате
  7. Для входных переменных строка должны быть правильная метка операционной системы в текстовом формате. Для выходных переменных значение колонки возвращается в том же формате
  8. Для входных переменных строка должны быть правильная метка операционной системы в формате представления системы. Для выходных переменных значение колонки возвращается в том же формате

I - Входные

O - Выходные

I/O - Входные и выходные

Описание некоторых функция OCI в терминах языка С

Общая структура OCI приложения с характерными функциями и их описание.

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

OLOG

Функция OLOG устанавливает соединение между OCI приложением и ORACLE сервером

 

olog(Lds_Def * lda, ub1 * hda, text * uid, sword uid1, text * pswd, sword pswd1, text * conn, sword conn1, ub4 mode);

 

OOPEN

Функция OOPEN открывает курсор в указанном соединении с сервером.

 

Oopen(Cda_Def * cursor, Lda_Def * lda, <text * dbn>, <sword dbn1>, <sword arsize>, <text * uid>, <sword uid1>);

 

OPARSE

Функция OPARSE делает лексический разбор SQL выражения или PL/SQL блока и ассоциирует их с открытым курсором.

oparse(Cda_Def *cursor, text *sqlstm,[sb4 sqll], sword defflg,ub4 lngflg);

 

ODESCR

Функция ODESCR описывает колонку для SQL запросов. Функция возвращает внутренний тип данных ORACLE для требуемой колонки, размер данных, точность и т.д., и т.п.

 

odescr(Cda_Def *cursor, sword pos, sb4 *dbsize, [sb2 *dbtype], [sb1 *cbuf], [sb4 *cbufl], [sb4 *dsize], [sb2 *prec], [sb2*scale], [sb2 *nullok]);

 

OBINDPS

Функция OBINDPS ассоциирует адреса переменных Вашей OCI программы с шаблонами в SQL предложении или PL/SQL блоке. В отличие от более старых функций связывания переменных OBINDPS позволяет делать операции типа PieceWise и может связывать массивы структур.

 

obindps(Cda_Def *cursor, ub1 opcode, text *sqlsvar, [sb4 sqlvl], ub1 *pvctx, sb4 progvl, sword ftype, <sword scale>, [sb2 *indp], [ub2 *alenp], [ub2 *rcodep], sb4 pv_skip,sb4 ind_skip, sb4 alen_skip, sb4 rc_skip, [ub4 maxsiz], [ub4 *cursiz], <text *fmt>, <sb4 fmtl>, <sword fmtt>);

 

ODEFINPS

Функция ODEFINPS определяет выходную переменную для определенной колонки, результирующей выборки в SQL запросах. Функция может быть использована при операциях типа PieceWise и при обработке в массиве структур.

 

odefinps(Cda_Def *cursor, ub1 opcode, sword pos, ub1 *bufctx, sb4 bufl, sword ftype, <sword scale>, [sb2 *indp],<text *fmt>, <sb4 fmtl>, <sword fmtt>, [ub2 *rlenp], [ub2 *rcodep], sb4 buf_skip, sb4 ind_skip, sb4 len_skip, sb4 rc_skip);

OEXEC

Функция OEXEC выполняет SQL выражение, ассоциирующееся с курсором.

 

oexec(Cda_Def *cursor);

 

OEXN

Функция OEXN выполняет SQL выражение. Если в качестве входных переменных был определен массив структур, то некоторое количество элементов, указанное в качестве параметра функции, будет одновременно участвовать в процессе.

 

oexn(Cda_Def *cursor, sword iters, sword rowoff);

 

OEXFET

Функция OEXFET выполняет SQL выражение. Если в качестве выходных переменных был определен массив структур, то некоторое количество элементов, указанное в качестве параметра функции, будет одновременно участвовать в процессе. Происходит сразу выборка (FETCH) нескольких строк из результирующего множества в массив структур.

 

oexfet(Cda_Def *cursor, ub4 nrows, sword cancel, sword exact);

 

OFETCH

Функция OFETCH извлекает данные для одной строки результирующего набора.

 

ofetch(Cda_Def *cursor);

 

OFEN

Функция OFEN похожа на OFETCH, однако, извлекает из результирующего набора не одну строку, а сразу несколько в массив структур

 

ofen(Cda_Def *cursor, sword nrows);

 

OCLOSE

Функция OCLOSE закрывает курсор, освобождает ресурсы на сервере и удаляет отношение между структурами LDA и CDA.

 

oclose(Cda_Def *cursor);

 

OLOGOF

Функция OLOGOF удаляет отношение между структурой LDA приложения, отсоединяет от сервера приложение и освобождает все ресурсы на сервере, связанные с этим соединением.

 

ologof(Lda_Def *lda);

 

Реализация OCI для ORACLE 7 в различных системах программирования.

Связь с ORACLE 7 сервером через OCI в Borland C++ 5.01

Для работы нам потребуются следующие файлы: OCIAPR.H, ORATYPES.H, OCIDFN.H, OCIW32.DLL и библиотека импорта для OCIW32.DLL, соответственно OCIW32.LIB. Обратите внимание на то, что для подключения в директиве INCLUDE необходим только один заголовочный файл OCIAPR.H, остальные вызываются из него. Есть одна тонкость при работе именно с C++. Заголовочный файл OCIAPR.H - это файл с описанием функций OCI для компилятора ANSI C, именно С, а не С++, поэтому его необходимо доработать для работы с C++. Доработка заключается в добавлении строк, указывающих компилятору, что прототипы функций, перечисленные в файле, есть экспортируемые из языка С. Делается это директивами:

//

#ifdef __cplusplus

extern "C" {

#endif /* __cplusplus */

//

........

Содержание файла OCIAPR.H

........

//

#ifdef __cplusplus

}

#endif

//

 

Все перечисленные Выше файлы ставятся стандартным инсталлятором ORACLE.

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

Класс связи с сервером ORA_Connect

Полное описание класса смотрите в исходных текстах ora_connect.h и ora_connect.cpp в примере к статье для Borland С++. Вот пример исходного текста описания класса:

 

class ORA_Connect{

protected:

Lda_Def * LDA;

ub1 * HDA;

text * sInUser;

text * sInPass;

text * sInServ;

public:

sword Ret;

ub2 RetCode;

char Active;

 

ORA_Connect(text * sUser, text * sPass, text * sServ);

~ORA_Connect(void);

char ORAconnect(void);

void ORAdisconnect(void);

};

 

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

В файле ora_connect.cpp находится определение методов для класса ORA_Connect. Конструктор резервирует память для структур LDA и HDA, а так же для других переменных. Самое важное, что там есть, я приведу непосредственно здесь:

 

this->LDA = (Lda_Def *)calloc(1, sizeof(Lda_Def));

this->HDA = (unsigned char *)calloc(1, HOSTDATAAREA);

memset(this->HDA, 0, HOSTDATAAREA);

 

Как видите, после определения структуры HDA необходимо заполнить 0 ее содержимое. Метод ORAconnect отвечает за связь с сервером:

 

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

char ORA_Connect::ORAconnect(void){

char Success;

Success = true;

if ( this->Ret == NOTCONNECTED ){

opinit(OCI_EV_TSF); //Define Thread-safe envaronment

this->Ret = olog(this->LDA,

this->HDA,

this->sInUser,

strlen(this->sInUser),

this->sInPass,

strlen(this->sInPass),

this->sInServ,

strlen(this->sInServ), OCI_LM_DEF); //Bloking mode

if ( this->Ret != 0 ){

Success = false;

this->Active = false;

};

this->RetCode = this->LDA->rc;

if ( this->RetCode == 0 ) this->Active = true;

};

return Success;

};

 

Центральное положение в методе занимают OCI вызовы - OPINIT, который переводит OCI приложение, в данном случае, в режим сохранения мульти-поточности, и OLOG, который и устанавливает связь с сервером. Для связи необходимы структуры LDA, HDA, информация о пользователе и режим выполнения OCI процедур для данного соединения. В конкретном примере стоит режим выполнения с блокировкой основного процесса. Может быть это и не столь быстро, но с моей, субъективной, точки зрения на много надежней. Обратите внимание на то, что функции OLOG требуется не только символьная строка, описывающая, например, пользователя, но и ее длина. Нужно быть весьма аккуратным при использовании символьных строк в OCI вызовах. В документации продекларировано, что вместо длин строк можно писать -1, при этом для строк, заканчивающихся на 0, размер будет определен автоматически. Это не всегда работает. Лучше определять длину строки принудительно. В примере, предоставленном вместе со статьей, если в диалоговом окошке, спрашивающем пароль и пользователя, не вбивать никакой информации (получается пустая строка), то программа выдает exception на LOGON. Так как это демонстрационный пример, я не стал делать необходимых проверок, что, естественно, должно присутствовать при построении реального приложения для пользователя.

Работа метода ORAdisconnect основана на OCI функции OLOGOF:

 

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void ORA_Connect::ORAdisconnect(void){

if ( !this->Ret ){

this->RetCode = ologof(this->LDA);

this->Ret = NOTCONNECTED;

this->Active = false;

MessageBeep(10);

};

};

 

Единственный параметр - это структура LDA - защищенная переменная этого же класса. Отсоединение от сервера происходит только после проверки, а было ли оно перед этим установлено.

Теперь можно написать пример по использованию класса ORA_Connect. Код примера вешается на событие WM_COMMAND главного меню приложения при выборе пункта меню CM_CONNECT (исходный код mainwindow.cpp, процедура MainWindow::Dispatch):

 

case CM_CONNECT:

//

Connect_Dlg oDlgC(this, IDD_DIALOG_OLOG);

if ( oDlgC.Show() ){

ORA_Connect oConn( (text*)oDlgC.GetUser(),

(text*)oDlgC.GetPass(),

(text*)oDlgC.GetServ());

oConn.ORAconnect();

if ( oConn.Ret == 0 ){

MessageBox(this->Handle,

"Connect successfull!",

"-!-",

MB_OK);

oConn.ORAdisconnect();

if ( oConn.RetCode == 0 ){

MessageBox( this->Handle,

"DisConnect successfull!",

"-!-",

MB_OK);

} else {

MessageBox( this->Handle,

"DisConnect is failed!",

"-!-",

MB_OK);

};

} else {

MessageBox(this->Handle, "Connect is failed!", "-!-", MB_OK);

};

};

//

break;

Connect_Dlg - это класс модального диалогового окна, в которое вводится пользователь, пароль и имя сервера для SQL*Net. (В данном примере все три поля должны быть чем-то заполнены, если Вы не хотите Exception.) Если по окончании ввода была нажата кнопка OK (if( oDlgC.Show() ) ), то происходит коннект к желаемому серверу путем определения переменной oConn класса ORA_Connect. В качестве параметров для конструктора передается информация о пользователе и сервере из диалогового окошка:

 

ORA_Connect oConn( (text*)oDlgC.GetUser(),

(text*)oDlgC.GetPass(),

(text*)oDlgC.GetServ());

oConn.ORAconnect();

 

При успешном соединении высвечивается информационное окошко и происходит сразу без выполнения каких либо действий принудительное отсоединение от сервера:

 

oConn.ORAdisconnect();

 

Обратите внимание на то, что необязательно принудительно делать отсоединение, это происходит автоматически при вызове деструктора класса ORA_Connect:

 

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ORA_Connect::~ORA_Connect(void){

this->ORAdisconnect();

free(this->sInUser);

free(this->sInPass);

free(this->sInServ);

free(this->LDA);

free(this->HDA);

};

 

Но желательно, хотя бы по правилам хорошего тона программирования.

Как Вы, наверное, убедились ORA_Connect очень простой класс. Однако он весьма важен для дальнейшей работы с сервером. Фактически любой класс, который Вы, возможно, станете конструировать, будет нуждаться в информации о соединении, внутри которой все и будет происходить. Источником этой информации и есть класс ORA_Connect.

Теперь попробуем в открытом конекте выполнить SQL предложение. Для этого напишем класс ORA_Statement.

Класс выполнения SQL предложений ORA_Statement.

Основное предназначение данного класса - открытие курсора, лексический разбор SQL предложения, связывание входных параметров, определение параметров результирующей выборки, выполнение SQL предложения, извлечение данных, удовлетворяющих запросу. Все описание класса Вы найдете в исходных текстах ORA_Statement.h и ORA_Statement.cpp в примере к статье. Ключевыми свойствами класса являются переменные, содержащие объект текущего соединения, SQL выражение, структуру CDA для курсора:

 

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

class ORA_Statement{

 

protected:

 

ORA_Connect * connect;

unsigned char * SQLtext;

Cda_Def * Cursor;

short OpenCursor;

unsigned long ColumnCount;

RStruct * RawStructure;

RStruct * InputStructure;

unsigned char * OutputBuffer;

short * Indp;

unsigned char * InputBuffer;

short * IndpInput;

short * alenp;

char AllocOutputBuffer;

char AllocInputBuffer;

char lExec;

char eof;

unsigned long ParamNumber;

SelectListItem sliR;

 

private:

 

unsigned long RecordSize;

unsigned long RecordParamSize;

unsigned long Granulation;

unsigned long CurrGranule;

unsigned long LastGranule;

unsigned long RowsDone;

unsigned long ParamCount;

DescrParStruct * DescribeParam;

 

public:

 

unsigned char ErrMes[512];

 

ORA_Statement(ORA_Connect * oConn);

~ORA_Statement(void);

 

unsigned char * GetSQLText(void);

void SetSQLText(unsigned char * SQLstmt);

unsigned long GetSQLColumnCount(void);

char SQLExec(void);

void SQLFetch(void);

char SQLeof(void);

short SQLCommit(void);

short SQLRolBack(void);

unsigned char * SQLGetColumnName(DWORD pos);

unsigned long GetGranulation(void);

void SetGranulation(unsigned long x);

SelectListItem * FieldGet(DWORD pos);

unsigned char * SQLDescribeError(void);

unsigned short SQLGetLastError(void);

 

};

 

Конструктор ORA_Statement(ORA_Connect * oConn) инициализирует переменные, резервирует память и открывает курсор в соединении, указанном в качестве параметра:

 

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ORA_Statement::ORA_Statement(ORA_Connect * oConn){

...

this->Cursor = (Cda_Def *)calloc(1, sizeof(Cda_Def));

if ( oConn->Active ){

this->connect = oConn;

...

this->OpenCursor = oopen(this->Cursor,

this->connect->GetLDA(),

NULL,

-1,

0,

this->connect->GetUser(),

strlen(this->connect->GetUser()));

};

};

 

Деструктор закрывает курсор:

 

if ( !this->OpenCursor ) if ( oclose(this->Cursor) == 0 ) this->OpenCursor = -1;

 

и освобождает ранее зарезервированную память.

Для разбора SQL предложения, связывания параметров и определения параметров результирующего набора служит метод SetSQLText.

В начале он просто переписывает SQL выражение, переданное в качестве параметра, во внутреннюю переменную класса. При этом происходит замена всех управляющих символов (в том числе и пары перевод строки - возврат каретки) на пробелы. Это необходимо для того, что, OCI процедуры не понимают управляющих символов в SQL предложении (или PL/SQL блоке). После этого SQL выражение подвергается лексическому разбору:

 

oparse(this->Cursor, this->SQLtext, strlen(this->SQLtext), 1, 2);

 

Происходит описание колонок результирующего набора:

 

if ( odescr( his->Cursor,

pos,

&dbSize,

&InernalDatType,

ColumnName,

&LengthOfColumnName,

&DisplaySize,

&PrecOfNum,

&ScalOfNum,

&NullOk) != 0 ){

break;

} else {

lL = LengthFromORATypes(InernalDatType, dbSize, DisplaySize, ScalOfNum);

ColumnName[LengthOfColumnName] = 0;

ColumnName[LengthOfColumnName+1] = 0;

RS_S.S_DBSIZE = dbSize;

RS_S.S_INERNALTYPE = InernalDatType;

strcpy(RS_S.S_COLNAME, ColumnName);

RS_S.S_DISPLAYSIZE = DisplaySize;

RS_S.S_PREC = PrecOfNum;

RS_S.S_SCALE = ScalOfNum;

RS_S.S_NULLOK = NullOk;

RS_S.S_OFFSET = AccauntBytes;

RS_S.S_EXTERNALTYPE = ExternalTypes(InernalDatType, ScalOfNum);

RS_S.S_LENGTH = lL;

this->RawStructure[pos-1] = RS_S;

AccauntBytes = AccauntBytes + lL;

};

 

Результат описания помещается во внутренний массив this->RawStructure, который есть массив некоторых структур под названием Rstruct . Поля структуры Rstruct полностью описывают одну колонку результирующей выборки. Обратите, пожалуйста, внимание на функции ExternalTypes и LengthFromORATypes. Функция ExternalTypes ставит соответствие между внутренним типом данных и внешним типом данных для OCI программы. Если Вы посмотрите на тело этой функции, то поймете, что не все типы данных поддерживаются конструируемым классом. Это сделано, во - первых, из соображений экономия моего времени, а во- вторых, для реализации Ваших собственных фантазий на данную тему. В частности не поддерживаются поля типа LONG и LONG ROW. Я специально не стал делать реализацию операций типа PieceWise, переложив весьма не простую задачу на Ваши плечи, успехов Вам, дорогие господа программисты.

По результатам работы программы, описания колонок результирующего набора, определяются области памяти для выборки значений этого набора в OCI приложении. Делается это функцией odefinps. Выходной буфер для результирующего набора организован так, что может хранить сразу несколько строк выборки. Количество строк определяется параметром Granulation, по умолчанию ему присваивается 20 строк, но это дело можно поменять методом void SetGranulation(unsigned long x). Вот пример вызова odefinps:

 

if ( odefinps( this->Cursor,

1,

pos,

(ub1 *)sP,

this->RawStructure[pos-1].S_LENGTH,

this->RawStructure[pos-1].S_EXTERNALTYPE,

0,

this->Indp,

NULL,

0,

0,

0,

0,

AccauntBytes,

sizeof(short)*this->ColumnCount,

0,

0) != 0 ){

break;

};

 

Собственно адрес связывания есть переменная (ub1 *)sP, которая инициализируется перед этим значением переменной класса - указателем на буфер, плюс смещение для колонки под номером pos:

 

dwW = this->RawStructure[pos-1].S_OFFSET;

sP = this->OutputBuffer+dwW;

 

Еще нам потребуются метод выполнения SQL предложения - char ORA_Statement::SQLExec(void), метод выборки строк результирующего набора - void ORA_Statement::SQLFetch(void и метод выборки текущих значений полей - SelectListItem * ORA_Statement::FieldGet(DWORD pos). Метод SQLExec(void) делает элементарные проверки на тип исполняемого выражения и в зависимости от этого выполняет одну из OCI процедур oexfet, oexec или oexn. При этом поддерживается механизм Grannulation - выборки данных блоками. Надо сказать, что пользователь (в данном контексте - это программист) механизма Grannulation не замечает на уровне выполнения методов. Т.е. он (программист) делает вначале SQLExec , потом SQLFetch , как ему кажется, по одной строке, хотя внутри они буферизированы.

Вся работа метода SQLFetch(void) сводится к периодическому (при очередном заполнении буфера Grannulation) вызову ofen для пополнения буфера новыми значениями из результирующей выборки.

Далее нам осталось написать метод, который вытаскивает значения полей для текущей записи по номерам этих полей: FieldGet(DWORD pos). По существу, он, в конечном итоге, выдает просто ссылку на буфер, до этого определенный при помощи функции odefinps в методе определения SQL предложения SetSQLText:

 

this->sliR.uValue = (void*)(this->OutputBuffer+dwW);

 

Где dwW определяется с учетом номера поля и текущей строки в буфере Granulation.

 

После определения двух описанных классов общая схема Вашего OCI приложения может выглядеть следующим образом:

 

ORA_Connect oConn((text*)”USER”, (text*)”PASSWORD”, (text*)”SERVER”);

oConn.ORAconnect();

if ( oConn.Ret == 0 ){

ORA_Statement oStmt(&oConn);

oStmt.SetSQLText(“ANY SQL STATEMENT”);

oStmt.SQLExec();

if ( !oStmt.SQLGetLastError() ) {

oStmt.SQLCommit();

if ( oStmt.GetSQLColumnCount() > 0 ){

while ( !oStmt.SQLeof() ){

for (i=1;i<=oStmt.GetSQLColumnCount();i++){

oStmt.FieldGet(i);

};

oStmt.SQLFetch();

};

};

} else {

MessageBox(this->Handle, oStmt.SQLDescribeError(), "-!-", MB_OK);

};

} else {

MessageBox(this->Handle, "Connect is failed!", "-!-", MB_OK);

};

 

Что и реализовано не без успеха в двух примерах, которые Вы найдете в исходных текстах mainwindow.cpp - void MainWindow::STMT_Test1(void) и void MainWindow::STMT_Test2(void). Первый пример выдает Вам диалоговое окошко вида:

После нажатия на OK происходит выполнение SQL предложение или PL/SQL блока, которое Вы вбили в MultyLine Edit под названием SQL Statement.

Второй пример несколько сложней в том плане, что он позволяет Вам смотреть результаты Вашего SQL предложения в том случае, если это запрос. После ввода пользователя пароля и сервера на экране появляется диалоговое окно:

Вбейте запрос в верхнее окошко, нажмите кнопку “Выполнить” и Вы получите в нижнем окошке результирующую выборку, при условии, что все типы в выборке поддерживаются данной версией класса ORA_Statement.

Связь с ORACLE 7 сервером через OCI в CA-Visual Objects 2.x

Общие замечания по работе CA-Visual Objects 2.x с OCI

Система программирования CA-Visual Objects 2.x является очень мощным инструментом построения приложений управления базами данных в формате, наследованном от DBASE. Речь идет о форматах настольных систем (DESCTOP Systems) таких, как DBFNTX, DBFCDX, DBFMDX, DBFNDX. В систему входит интеллектуальный 32 разрядный компилятор с языка высокого уровня, называемого Visual Objects. Огромная библиотека классов, инструментарий визуального программирования и набор драйверов для манипулирования настольными базами данных с соответствующими классами. Система полностью объектно-ориентированная и рассчитана на работу под операционными системами Windows NT и Windows 95. Для общения с SQL серверами в CA-Visual Objects предусмотрен специальный механизм, реализованный в виде набора классов, работа которого основана на технологии Microsoft под названием ODBC.

Работу с SQL серверами в CA-Visual Objects через ODBC следует признать вполне удовлетворительной, но, к сожалению, не лишенной определенных сложностей и неудобств, присущих всем универсальным механизмам (таким как ODBC, BDE). Кроме того, набор SQL классов CA-Visual Objects является далеко не совершенным механизмом. Это приводит пользователей в легкое раздражение.

Естественно, что при такой постановке вопроса, было бы очень неплохо иметь свои классы для связи с серверами баз данных. Как это делается для конкретного примера с сервером ORACLE на основе OCI я и попытаюсь продемонстрировать в данном разделе.

Для связи с CALL интерфейсом ORACLE первое, что необходимо сделать в CA-Visual Objects - это прописать интерфейс в терминах языка Visual Objects. Сам интерфейс поставляется корпорацией ORACLE в терминах языка С (что вполне естественно). Это значит, что структуры данных и прототипы функций из заголовочных файлов OCI должны быть переписаны на CA Visual-Objects.

Далее проектируется система классов, основой которой являются вызовы CALLинтерфейса. Для общения с ORACLE сервером мною были спроектированы следующие классы: ORA_Connect, ORA_Statement и ORA_Server.

ORA_Connect - класс, отвечающий за связь с сервером и хранящий соответствующие структуры для этой связи - LDA и HDA.

ORA_Statement - класс, содержащий методы управления курсором и выполняющий любые SQLпредложения и PL/SQL блоки.

ORA_Server - наследник от стандартного класса CA-Visual Objects DataServer. Позволяет выводить результирующий набор с сервера в стандартном окне CA-Visual Objects - DataWindow. Размер результирующего набора не имеет значения, если Вас не интересует скорость выборки данных из конца источникаJ .

В данных классах реализованы практически все возможности ORACLE сервера версии 7.3.3 за исключением работы с данными типа LONG и LONG RAW. Считайте, что сделано это намерено. Иначе данная разработка была бы коммерческим продуктом. Кроме того, добавив в существующую версию работу с указанными выше типами, Вы, наверняка, хорошо поймете работу программы и без труда исправите ошибки, а также доработаете ее под свои конкретные нужды самостоятельно. Я считаю, что это весьма важно.

OCI в терминах языка CA-Visual Objects 2.x

Не думаю, что перевод структур и прототипов функций из OCIW32.DLL,описанных в заголовочных файлах OCIAPR.H, ORATYPES.H, OCIDFN.H для опытных программистов вызовет какие- либо сложности. Все, что необходимо для работы находится в модуле Visual Objects под названием OCI в поставляемом со статьей приложении OCI.

Для примера приведу описание структуры LDA и нескольких наиболее типичных функций:

 

STRUCTURE cda_def

MEMBER v2_rc AS SHORTINT

MEMBER ft AS WORD

MEMBER rpc AS DWORD

MEMBER peo AS WORD

MEMBER fc AS BYTE

MEMBER rcs1 AS BYTE

MEMBER rc AS WORD

MEMBER wrn AS BYTE

MEMBER rcs2 AS BYTE

MEMBER rcs3 AS SHORTINT

MEMBER rid AS RID

MEMBER ose AS SHORTINT

MEMBER chk AS BYTE

MEMBER rcsp AS PTR

MEMBER DIM rcs9[CDA_SIZE-_sizeof(cda_head)] AS BYTE

 

где:

 

STRUCTURE RID

MEMBER rd AS RD

MEMBER rcs7 AS DWORD

MEMBER rcs8 AS WORD

 

STRUCTURE RD

MEMBER rcs4 AS DWORD

MEMBER rcs5 AS WORD

MEMBER rcs6 AS BYTE

 

Как видите, все, что необходимо знать - это точное соответствие типов данных С и CA-Visual Objects.

Вот представление функции OBINDPS для CA-Visual Objects из модуля OCI приложения OCI:

 

_DLL FUNCTION obindps(cursor AS PTR, opcode AS BYTE, sqlvar AS PSZ,;

sqlvl AS LONG, pvctx AS PTR, progvl AS LONG,;

ftype AS SHORTINT, scale AS SHORTINT,;

indp AS PTR, alen AS PTR, arcode AS PTR,;

pv_skip AS LONG, ind_skip AS LONG, alen_skip AS LONG,;

rc_skip AS LONG,;

maxsiz AS DWORD, cursiz REF DWORD,;

fmt AS PSZ, fmtl AS LONG, fmtt AS SHORTINT);

AS WORD PASCAL:OCIW32.obindps

 

А вот описание функции OLOG:

 

_DLL FUNCTION olog(lda AS PTR, hda AS PTR,;

uid AS PSZ, uidl AS SHORTINT,;

pswd AS PSZ, pswdl AS SHORTINT,;

conn AS PSZ, connl AS SHORTINT,;

mode AS DWORD) AS SHORTINT PASCAL:OCIW32.olog

 

Сравните его с описанием на С:

 

sword olog (struct cda_def *lda, ub1* hda,

text *uid, sword uidl,

text *pswd, sword pswdl,

text *conn, sword connl,

ub4 mode);

 

Описание типов Вы найдете в файле PRATYPES.H. Нехитрые правила помогут Вам достаточно быстро сориентироваться в типах. Так буква s перед типом указывает на знаковую переменную, u-на беззнаковую. Далее может следовать {word, b}, b, что значит BYTE, стоит цифра, указывающая количество байт в переменной. Если Вы встретите тип ub4 - знайте, что это беззнаковое целое, состоящее из 4 байт, sb1 - знаковое целое из одного байта, и т.д.

Описание text * просто указатель на строку, оканчивающуюся 0 (unsigned char *).

Для получения модуля OCI я просто тупо переделывал типы параметров и указывал где искать данную функцию (в OCIW32.DLL), а также указывал тип соглашения передачи параметров и возврата значений. В данном случае PASCAL. Работа нудная и кропотливая, если учесть, что таких функций 42. Но Вы можете расслабиться, я сделал за Вас эту работу ( не обольщайтесь на полное отсутствие ошибок J ).

Прямое использование OCI в CA-Visual Objects 2.x.

Классический пример использования OCI интерфейса с ORACLE сервером Вы найдете в модуле OCI First приложения OCI. Это пример использования вызовов “в лоб” OCI процедур для выполнения SQL предложений в среде CA-Visual Objects. Структура подпрограммы являет собой академический пример схемы OCI приложения, приведенной несколько ранее в статье.

В начале, если помните, определяются структуры LDA и HDA, затем происходит коннект к серверу:

 

STATIC LDA IS cda_def

STATIC Cursor IS cda_def

STATIC DIM HDA[HOSTDATAAREA] AS BYTE

 

IF (Ret := olog( @LDA,;

@HDA[0],;

AsPsz(sInUser),;

SLen(sInUser),;

AsPsz(sInPass),;

SLen(sInPass),;

AsPsz(sInServ),;

SLen(sInServ),;

OCI_LM_DEF)) == 0

 

. . .

 

ELSE

MessageBox( SELF:Handle(),;

AsPsz("Конект рухнул"+Str(Ret)+"/"+Str(LDA.rc)),;

PSZ("-!-"),;

MB_OK)

END IF

 

Открываем курсор и делаем лексический разбор SQL выражения:

 

IF (Ret := oopen( @Cursor, @LDA,;

NULL_PSZ, 0, 0, AsPsz(sInUser), SLen(sInUser))) == 0

sL := [select * from spravka where famil like 'МИ%ИК%']

IF (Ret := oparse(@Cursor, AsPsz(sL), SLen(sL), 1, 2)) == 0

. . .

ELSE

MessageBox( SELF:Handle(),;

PSZ( "Крах Лексический разбор statment "+;

Str(Ret)+" / "+AsString(Cursor.rc)),;

PSZ("-!-"),;

MB_OK)

ENDIF

Ret := oclose(@Cursor)

ELSE

MessageBox( SELF:Handle(),;

AsPsz("Крах открытия курсора "+;

Str(Ret)+" / "+AsString(Cursor.rc)),;

PSZ("-!-"),;

MB_OK)

ENDIF

 

Далее в цикле извлекаем информацию для каждой колонки результирующей выборки, если SQL выражение есть запрос. Делается это при помощи OCI вызова odescr. После заполнения соответствующих массивов и резервирования памяти для выходных значений выполняется связывание адресов памяти в OCI приложении с колонками результирующей выборки вызовом OCI процедуры odefin:

 

FOR pos := 1 UPTO NPOS

IF (Ret := odescr(@Cursor,;

pos,;

@MaxSize,;

@InernalDatType,;

ColumnName,;

@LengthOfColumnName,;

@MaxDisplaySize,;

@PrecOfNum,;

@ScalOfNum,;

@NullOk)) != 0

 

IF Cursor.rc == 1007

MaxPos := pos

EXIT

END IF

ELSE

AAdd( arrCursor,;

{MemAlloc(MaxDisplaySize),;

MaxDisplaySize,;

Mem2String(ColumnName, LengthOfColumnName)})

odefin( @Cursor,;

pos,;

arrCursor[pos, 1],;

MaxDisplaySize,;

SQLT_AFC,;

ScalOfNum,;

@indp,;

NULL_PSZ,;

0, 0, @rlen, @rcode)

END IF

NEXT

 

После связывания адресов - выполнение SQL предложения и выборка результата в DBF таблицу, которую потом можно показать в BREWSER-е:

 

oexec(@Cursor)

lL := 0

DBCREATE(TmpDB := CreatTmp(), arrStruct)

oDB := dbserver{TmpDB}

WHILE TRUE

IF (Ret := ofetch(@Cursor)) != 0

EXIT

ELSE

lL++

nel := 0

oDB:Append()

AEval(arrCursor,;

{|el| nel++,;

oDB:FIELDPUT(AsSymbol(el[3]),;

sL1 := Mem2String(el[1], el[2])) })

END IF

END WHILE

 

Обратите внимание на то, что подобная программа при связывании выходных значений с адресами памяти принудительно заставляет (пятый параметр odefin SQLT_AFC) ORACLE сервер конвертировать данные из выборки в символьные строки. Соответственно, программа не может переварить те типы данных, которые сервер не может переконвертировать в символьную строку, следовательно, не все запросы могут быть показаны в окне DataWindow, хотя выполнены могут быть все.

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

В результате работы описанной процедуры с запросом [select * from spravka where famil like 'МИ%ИК%'] получается уже знакомая Вам выборка из 7 строк из 30000 записей:

 

 

Класс ORA_Connect.

С подобным классом Вы уже знакомы по С++. Ничего принципиально нового в нем нет, а отличается он только языком реализации. Полное описание класса Вы найдете в модуле OCI Connect Class.

Конструктор класса ORA_Connect принимает 3 параметра: Имя пользователя, пароль пользователя и строка, определяющая алиас сервера в SQL*Net конфигурации. Кроме того, Вы можете задать эти три параметра в соответствующих свойствах класса, которые так и называются: User, Password, Server.

Конструктор инициализирует структуры LDA и HDA, а также некоторые другие переменные.

Деструктор класса, ORA_Connect:Axit(), вызывается автоматически, после уничтожения переменной, экземпляра класса. В его задачу входит отсоединение от сервера и освобождение памяти, зарезервированной конструктором для структур LDA, HDA.

Класс содержит методы соединения с сервером и отсоединения от него: ORA_Connect:ORAconnect(),ORA_Connect: ORAdisconnect().

Очень важным свойством класса является ACCESS метод LDA(), который осуществляет доступ по чтению к структуре LDA для всего окружения объекта класса. Без доступа к LDA структуре не может быть выполнено ни одно SQL выражение.

Тестовые примеры применения класса ORA_Connect Вы можете посмотреть в модуле Connect class test. Вот типичный код из этого модуля:

 

oC2 := ORA_Connect{"scott", "tiger", "main"}

IF oC2:ORAconnect()

MessageBox( SELF:Handle(),;

PSZ("Соединение успешно"),;

PSZ("-!-"), MB_OK)

ELSE

MessageBox( SELF:Handle(),;

AsPsz("Крах соединения"+AsString(oC2:RetCode)),;

PSZ("-!-"), MB_OK)

END IF

 

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

Класс ORA_Statement.

Как Вы, наверное, уже догадались - этот класс является полной аналогией класса на языке C++. Описание класса находиться в модуле OCI SQL Statement Class.

Конструктор ORA_Statement:Init(oConn) принимает один параметр - экземпляр класса ORA_Connect, который перед этим должен быть инициализирован, и, кроме того, должно быть установлено соединение с сервером при помощи метода ORA_Connect:ORAconnect(). Конструктор устанавливает в начальные значения необходимые переменные, резервирует память под структуру CDA, открывает курсор вызовом OCI процедуры OOPEN и регистрирует деструктор для экземпляра класса, который вызывается автоматически при операции уничтожения. Деструктор ORA_Connect:Axit() закрывает курсор и освобождает память.

После инициализации экземпляра класса, необходимо указать SQL выражение для выполнения. Для этого существует ASSIGN метод SQLtext.

В программе, использующей класс ORA_Statement, присвоение SQL выражения для выполнения выглядит так:

 

oConn := ORA_Connect{“scott”, “tiger”, “”}

oStmt := ORA_Statement{oConn}

oStmt:SQLtext := [select * from spravka where famil like 'МИ%ИК%']

 

На деле же ASSIGN метод SQLtext весьма сложен. Первое, что происходит - это лексический разбор SQL выражения:

 

oparse(SELF:Cursor, AsPsz(SELF:SQLtext), SLen(SELF:SQLtext), 0, 2)

 

Сразу должен заметить, что DDL команды (при установленном 4 параметре oparse в 0) выполняются сразу в момент вызова oparse, поэтому для них ни только не обязательно вызывать метод SQLExec, но и крайне нежелательно, так как это может привести к выдаче сервером сообщения об ошибке, например, набор вызовов:

 

oConn := ORA_Connect{“scott”, “tiger”, “”}

oStmt := ORA_Statement{oConn}

oStmt:SQLtext := [drop table emp]

oStmt:SQLExec()

 

приведет к ошибке “Table or view does not exist”. И, действительно, получается, что Мы пытаемся два раза удалить одну и ту же таблицу. Первый раз она успешно удаляется, а второй выдается ошибка.

Далее OCI функцией ODESCR программа пытается описать каждую колонку результирующей выборки. Результаты этого описания попадают в конечном итоге во внутренние массивы.

 

odescr( SELF:Cursor,;

pos,;

@dbSize,;

@InernalDatType,;

ColumnName,;

@LengthOfColumnName,;

@DisplaySize,;

@PrecOfNum,;

@ScalOfNum,;

@NullOk)

 

В процессе описания колонок наращиваются переменные SELF:ColumnCount и AccauntBytes. Первая переменная хранит число колонок в результирующей выборке, а вторая -количество байт буфера одной строки выборки (не учитывая, естественно LONG и LONG RAW). Информация о колонках используется для резервирования памяти под выходной буфер, куда определяется вывод (OCI функция odefinps) для результирующего набора.

Программа резервирует буфер сразу для нескольких строк, количество которых определяется HIDDEN переменной Granulation , по умолчанию установленной в значение 20. Однако, Вы в праве установить свое значение при помощи ASSIGN метода Granulation().

В дальнейшем при выполнении SQL выражения и при проходе по выборке происходит буферизация в буфер Granulation , что существенно ускоряет работу класса. Естественно, описание методов SQLExec и SQLFetch потребовало значительно больших усилий от меня, так как они должны были обладать достаточной степенью интеллектуальности для поддержки подобного механизма.

Класс ORA_Statement поддерживает буферизированный проход по курсору только в одном направлении, собственно как и OCI интерфейс в целом. Метод SQLFetch при каждом своем вызове передвигает логический указатель записи на единицу вперед, делая возможным извлечение значений полей для этой записи при помощи метода FIELDGET(). Если SQLFetch определит, что была попытка перескочить за границы буфера Granulation, то происходит заполнение буфера следующими значениями из результирующей выборки, при этом постоянно отслеживается значение переменной EOF, по которой можно определить достигнут ли конец источника данных.

Класс ORA_Statement обладает еще одной важной особенностью: он может связывать входные массивы структур с SQL предложением. Это обеспечивает передачу на сервер до 31000 строк структурированных параметров, что важно, например, при операциях INSERT с целым массивом значений. Должен заметить, не без удовольствия, что подобную функцию не поддерживают ни BDE, ни ODBC.

Реализовано это двумя методами. Первый - SQLDefParam(aDescribeParam) просто описывает структуру входного массива параметров. Ему передается массив массивов со структурой следующего содержания:

 

{":Par1", Type, Length}

 

Первый элемент массива - строка, содержащая параметр для SQL выражения. Второй элемент - строка, состоящая из одного символа: тип этого параметра. В данной версии поддерживается 4 типа параметров:

 

STRING - "C"

LONGINT, SHORTINT, INT, BYTE, WORD, DWORD - "I"

REAL4, REAL8, FLOAT - "F"

DATE - "D"

 

Третий элемент указывается только для типа STRING и означает длину строки, во всех остальных случаях - 0.

Пример:

 

oStmt:SQLtext := [INSERT INTO VV VALUES(:Par1, :Par2, :Par3...)]

oStmt:SQLDefParam({{":Par1", "I", 0}, {":Par2", "C", 10}, {":Par3", "F", 0}, ...})

 

Этот пример показывает как в абстрактную таблицу VV вставляются значения из массива, определенные через параметры SQL предложения. Мы видим, что первый параметр - целое число, второй - строка длинной 10 символов и третий - число с плавающей точкой.

Второй метод непосредственно передает массив с параметрами для связывания с SQL выражением:

 

oStmt:SQLPassParam({{10, 20, 30}, {"ddd", "gfhfg", "jjjj"}, {10.5, 11.3, 12.4}, ...})

 

Как видите, в примере передаются 3 строки с конкретными значениями.

Основа работы метода SQLPassParam - вызов OCI процедуры obindps, которая связывает входной массив структур с SQL выражением.

После вызова

 

oStmt:SQLExec()

 

Все 3 строки незамедлительно попадут в базу.

Помимо вышеперечисленных свойств и методов класс ORA_Statement содержит рад вспомогательный методов, таких как вывод сообщения о последний ошибке с сервера ( SQLDescribeError() ), фиксация и откат транзакции ( SQLRolBack(), SQLCommit() ), выдача информации о структуре выходного набора ( ACCESS DBStruct ), и некоторые другие.

Пример выполнения запроса с использованием класса ORA_Statement.

Для проверки работоспособности класса ORA_Statment мной был написан пример, текст которого находится в модуле Statment Test Of OCI. В примере я присоединяюсь к серверу при помощи класса ORA_Connect. В диалоговом окошке вида:

пользователь имеет возможность ввести любое выражение, доступное для выполнения ORACLE сервером. После нажатия кнопки “Выполнить” выражение из диалогового окошка присваивается ASSIGN переменной SQLtext инициализированному объекту класса ORA_Statment. Объект этот выполняет полученное выражение. Если результат имеет на выходе несколько строк (больше, чем 0), то объект пробегает по всем строкам результирующей выборки (метод Fetch()) и заносит полученную выборку во внутренний массив, который затем показывается в окне DataWindow при помощи специального ArrayServer, написанного как раз для этих целей (модуль Array Server):

.

В тексте программы, все выглядит так:

 

oPass := Pass{SELF, {"SCOTT", "TIGER", ""}}

oPass:Show()

oConn := ORA_Connect{oPass:User, oPass:PassWord, oPass:Server}

IF oConn:ORAconnect()

oStmt := ORA_Statement{oConn}

oRW := ReqWin{SELF}

oRW:oDCMLE_E:CurrentText := [select * from emp]

oRW:Show()

oStmt:SQLtext := AllTrim(oRW:oDCMLE_E:CurrentText)

IF oStmt:SQLExec()

oStmt:SQLCommit()

IF oStmt:SQLColumnCount > 0

LastR := 0

arrF := {}

WHILE !oStmt:SQLeof

LastR ++

arrR := {}

FOR pos := 1 UPTO oStmt:SQLColumnCount

AAdd(arrR, oStmt:FIELDGET(pos))

NEXT

AAdd(arrF, arrR)

oStmt:SQLFetch()

END WHILE

IF LastR > 0

oDW := DataWindow{SELF}

arrNames := {}

FOR i := 1 UPTO oStmt:SQLColumnCount

AAdd( arrNames,

oStmt:SQLGetColumnName(i))

NEXT

oArrServ := ArrayServer{arrNames, arrF}

oDW:Use(oArrServ)

oDW:ViewAs(#BrowseView)

oDW:Show()

END IF

END IF

ELSE

oStmt:SQLRolBack()

MessageBox( SELF:Handle(),

PSZ('Error execute statement!'), PSZ('-!-'), MB_OK)

ENDIF

ELSE

MessageBox( SELF:Handle(),

PSZ('Connect error!'), AsPsz(oConn:RetCode), MB_OK)

END IF

 

Пример связывания входного массива в классе ORA_Statement.

Класс ORA_Statment в состоянии получить выходной набор, а также передать SQL предложению в качестве параметров некоторые значения. Кроме того, класс позволяет связывать целый массив входных значений с SQL предложением. Эту возможность демонстрирует пример, находящийся в модуле Bind Test of OCI. Работа примера основана на методах SQLDefParam и SQLPassParam. Первый метод описывает входные параметры, второй - непосредственно передает массив с параметрами для выполнения SQL предложения:

 

oStmt := ORA_Statement{oConn}

oStmt:SQLtext := [insert into emp (empno, ename, job, mgr, deptno) values (:empno, :ename, :job, :mgr, :deptno)]

oStmt:SQLDefParam( { {":empno", "I", 0},;

{":ename", "C", 10},;

{":job", "C", 9},;

{":mgr", "I", 0},;

{":deptno", "I", 0};

})

oStmt:SQLPassParam({ {72, 73, 74, 75},;

{"dddd", "ffff", "gggg", "hhhfd"},;

{"ytr", "trtr", "fhgf", "yry"},;

{20, 20, 20, 20},;

{10, 20, 10, 30};

})

oStmt:SQLexec()

 

После выполнения приведенного куска программы в таблице EMP окажется целиком массив, указанный в методе SQLPassParam (если, естественно, будет все в порядке с ограничением целостности.)

Пример миграции данных из DBASE в ORACLE с использованием класса ORA_Statement.

Это наиболее интересный пример с точки зрения функциональности. Программа делает экспорт данных их таблицы DBF в таблицу ORACLE. Делается это при помощи класса ORA_Statment и двух, Вам уже знакомых, методов - SQLDefParam и SQLPassParam. Пример Вы можете посмотреть в модуле ExpLORE.

После инициализации объектов ORA_Connect и ORA_Statment путем присваивания ASSIGN переменной SQLText класса ORA_Statment создается пустая таблица в схеме ORACLE необходимой структуры. Для этого выполняются последовательно скрипты DrTab.sql и CrTab.sql. (Просто переносится содержимое скриптов в переменную SQLText). Так как DDL команды выполняются на стадии PARSE, дальнейшей обработки не требуется.

Значению переменой SQLText присваивается текст собственно с INSERT с параметрами, определяется тип параметров методом SQLDefParam и запускается цикл по BDF файлу, который заполняет динамический массив InArr . По достижении переменной цикла определенного периодического значения весь массив одним методом SQLExec отправляется на сервер.

Обратите внимание на то, что время, необходимое для работы программы, затрачивается только на перебор таблицы DBF в среде Visual Objects. Процесс переноса записей на сервер происходит почти мгновенно!

В этом очень легко убедиться, запустив тот же цикл по DBF без отсылки информации на сервер и измерив время работы.

Класс ORA_Server.

Класс ORA_Server - это то, о чем, наверное, многие мечтали может быть когда-то давно, когда пытались писать на CA-Visual Objects, работая с ORACLE через его ODBC классы.

Так или иначе, может быть кому-то это понадобится и сейчас. ORA_Server - это класс, наследник от стандартного DataServer, переопределяющий его основные методы для навигации по результирующей выборке от ORACLE сервера. Это значит, что Вы легко можете объект класса ORA_Server прицепить к стандартному объекту DataWindow обычным USE и получить данные с сервера в привычном окне Browser и use-ать их там сколько душе угодно.

Сразу оговорюсь по поводу размера результирующей выборки. Дело в том, что ORACLE поддерживает скролируемый курсор ТОЛЬКО (надеюсь, пока) в одном направлении - прямо. Это идеология ORACLE - Вы можете беситься по этому поводу сколь угодно долго, но идеология ORACLE от этого не поменяется и будет в корне отличаться от xBase технологии, где в любой момент времени можно бегать по базе в любую сторону: и в начало, и в конец.

При написании класса ORA_Server приходилось искать некий компромисс между двумя идеологиями. Компромисс заключается в использовании буферов при навигации по выборке со сложным механизмом закачивания очередного текущего буфера в память машины. Это стандартный подход, он применяется как в родных продуктах ORACLE, так и в продуктах других производителей. BDE и ODBC работают точно также: через замещаемые буфера.

Естественно, что если Вы находитесь в начале какого-нибудь 10000 буфера и делаете попытку переместиться на запись назад, то серверу ничего не остается делать как выполнить Ваш запрос заново и отмотать по новой все 9999 буферов для того, что бы Вы смогли полюбоваться на требуемую Вами запись. Если предположить, что буфер содержит 100 записей, то ORA_Server должен произвести 999900 команд FETCH, что, естественно, требует времени и немалого.

Поэтому при работе с классом ORA_Server Вам придется принять правила игры ORACLE и применять SELECT по прямому его назначению: выборки из огромного массива информации нескольких записей, удовлетворяющих определенному критерию. А программирование “А-ля CLIPPER”: - засосать в Browser всю таблицу в пару миллионов записей и целиком ее там редактировать оставьте, пожалуйста, навсегда.

Реально, более- менее приемлемо с классом ORA_Server можно работать при выборке в 10-15 тысяч записей.

Я не буду расписывать подробно как устроен внутри класс ORA_Server , это можно посмотреть в модуле ORA Server , но его использование очень элегантно, так как органично входит во внутреннюю организацию CA-Visual Objects.

Пример использования класса ORA_Server.

Без комментариев:

 

oPass := Pass{SELF, {"SCOTT", "TIGER", ""}}

oPass:Show()

oConn := ORA_Connect{oPass:User, oPass:PassWord, oPass:Server}

IF oConn:ORAconnect()

oStmt := ORA_Statement{oConn}

oRW := ReqWin{SELF}

oRW:oDCMLE_E:CurrentText := [select * from emp]

oRW:Show()

oStmt:SQLtext := AllTrim(oRW:oDCMLE_E:CurrentText)

oServ := ORA_Server{oStmt, SELF}

oW := DataWindow{SELF}

oW:Menu := StandardShellMenu{}

oW:Use(oServ)

oW:ViewAs(#BrowseView)

oW:Show()

ELSE

MessageBox(SELF:Handle(),

PSZ('Connect error!'), AsPsz(oConn:RetCode), MB_OK)

END IF

 

Класс ORA_Server с входными параметрами.

>

Объекту класса ORA_Server передается при инициализации в качестве параметра объект класса ORA_Statment. Если помните, этот класс обладает возможностью передавать параметры в исполняемое SQL предложение. Используя эту возможность, можно делать параметризированный сервер. Пример такого сервера Вы можете найти в модуле ORA Server Test 1.

На этом, пожалуй, все, относительно работы CALL интерфейса с CA-Visual Objects.

 

Задание на дом:

Доработайте существующие классы для работы с полями типа LONG. Сконструируйте класс ORA_StoredProc и Вы получите полноценную систему для работы с ORACLE 7 в среде программирования CA-Visual Objects. Успехов.

Связь с ORACLE 7 сервером через OCI в Borland С++Builder 3

По существу, реализация CALL интерфейса для C++ Builder является переносом классов, рассмотренных мною ранее для Borland C++ 5.01, в иерархию библиотеки VCL. Перенос предполагает использование усовершенствованных механизмов формирования компонент таких, как публикуемые свойства и события, которые автоматом появляются в Object Inspector и делают программирование удовольствием.

Преимущества компонентной системы C++ Builder перед классовой системой C++ неоспоримы и во много раз увеличивают скорость разработки приложений. Сравните заголовочные файлы C++ и C++Builder для класса и соответственно компонента ORA_Connect:

 

Описание для C++ я уже приводил.

 

А вот для C++Builder:

 

class PACKAGE ORA_Connect : public TComponent

{

private:

protected:

 

Lda_Def LDA;

ub1 HDA[HOSTDATAAREA];

AnsiString sInUser;

AnsiString sInPass;

AnsiString sInServ;

Bool IsActive;

Lda_Def* __fastcall GetLda(void){return &LDA;};

 

public:

 

ub2 RetCode;

__fastcall ORA_Connect( TComponent* Owner, AnsiString sUser,

AnsiString sPass,

AnsiString sServ);

__fastcall ~ORA_Connect(void);

char __fastcall ORAconnect(void);

void __fastcall ORAdisconnect(void);

AnsiString __fastcall GetUser(void);

void __fastcall SetUser(AnsiString sUser);

AnsiString __fastcall GetPassword(void);

void __fastcall SetPassword(AnsiString sPass);

AnsiString __fastcall GetServer(void);

void __fastcall SetServer(AnsiString sServer);

bool __fastcall GetActive(void);

void __fastcall SetActive(bool ActFlag);

__property Lda_Def * Lda = {read = GetLda};

 

__published:

 

__property AnsiString User = {read = GetUser, write = SetUser};

__property AnsiString Password = {read = GetPassword, write = SetPassword};

__property AnsiString Server = {read = GetServer, write = SetServer};

__property bool Active = {read = GetActive, write = SetActive};

 

};

 

Обратите внимание на секцию __published, члены которой попадают в Object Inspector.

 

Их мы там и обнаруживаем:

Мы можем сразу из Object Inspector установить свойство Active в TRUE, тогда произойдет коннект к серверу в Design Time, если правильно установлены параметры коннекта – User, Password, Server. Сам компонент ORA_Connect переносится на форму из палитры компонент, в которой появляется после инсталляции компонента:

В Run Time мне достаточно установить Active в TRUE для установки соединения. Вот реакция на нажатие кнопки Connect:

 

void __fastcall TForm1::Button1Click(TObject *Sender)

{

ORA_Connect1->Active = true;

}

 

А вот реакция на Disconect:

 

void __fastcall TForm1::Button2Click(TObject *Sender)

{

ORA_Connect1->Active = false;

}

 

Работа примера напоминает известный мультфильм – входит и выходит. Попробуйте сами.

Класс ORA7_Satment

В отличии от аналога на C++ класса компонент для С++ Builder значительно усовершенствован. В него добавлены методы перемещения не только вперед, но и назад, в начало выборки и в конец. Это потребовало от меня достаточных усилий. Кроме того, компонент спроектирован таким образом, что он тесно взаимодействует со средой C++ Builder и с другими компонентами, с которыми он работает в одной связке. Для нормальной работы компонента первое, что необходимо сделать – это установить коннект с сервером, что делает компонент ORA7_Connect. Далее указывается SQL выражение и объект переводится в активное состояние при условии, что связанный с ним объект ORA7_Connect тоже активный.

Среда C++ Builder накладывает дополнительные функции в манипулировании компонентами в дизайн тайме. Если Вы в данной ситуации решили, например, удалить с формы компонент ORA7_Connect или перевести его в неактивное состояние, то связанный с ним объект ORA7_Statment должен отреагировать подобающим образом. При удалении ORA7_Connect – обрубить ссылку на него, сделав, перед этим, свой статус не активным. При переводе в неактивное состояние ORA7_Connect перевести в неактивное состояние ORA7_Statment. И так далее, и тому подобное… Надо сказать, что это весьма сильно затрудняет задачу при проектировании компонент, но при этом они становятся очень удобны в применении.

Движение по результирующей выборке взад вперед осуществляется при помощи перегружаемых буферов. В ORACLE 7 реализован проход по выборке только в одном направлении – это и создает дополнительные сложности. При попытке перехода на предыдущий буфер приходиться пере открывать курсор и отматывать его до нужного места. Это долго, трудно реализуемо и вообще чревато ошибками, поскольку ORACLE не гарантирует порядок произвольной выборки при пере открытии курсора с одним и тем же SQL предложением. Кроме того, сами данные на сервере к моменту пере открытия могут измениться. Именно поэтому компонент целесообразно использовать при малых выборках, причем устанавливать буфер записей (свойство Portion) в значение, значительно превышающее возможное количество выбранных записей. При этом следует помнить, что ORACLE может выгрести за один раз из базы не более чем 32768 записей.

Если Вы используете только прямой проход по выборке (не используете механизм перегрузки буферов), то компонент ORA7_Statment – это то, что Вам нужно и размер выборки при этом не имеет ни какого значения (построение отчета, например).

Пример использования класса ORA7_Satment

Исходные тексты, к примеру, находятся в директории Statment Test 1. Примером может являться окно, разделенное сплитером, в верхней части которого Вы можете написать любое допустимое выражение для ORACLE 7, а в нижней выдаются результаты работы. В меню есть режим установки коннекта с сервером. Без коннекта работа, естественно, не возможна. В любой момент вы можете поменять коннект и продолжать работу уже с другим сервером. Если SQL выражение, которое Вы ввели в верхнюю часть экрана, есть запрос, то нижняя заполняется результатами этого запроса в StringGrid. Процесс заполнения грида Вы можете прервать в любой момент времени. Не рекомендуется заполнять грид количеством строк более чем 20-30 тысяч.

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

Выполнение SQL выражения происходит при установке в TRUE свойства Active экземпляра компонента ORA7_Statment. Выборка данных осуществляется путем вызова метода RawFieldGet , который выгребает “сырые” данные, т.е. данные в формате ORACLE, плюс набор специальных атрибутов, указывающих размер этих данных, наличие NULL значений в данных, ошибок и т.д. Все это подготавливается в специальной структуре под названием SelectListItem. Перемещение происходит при помощи метода SQLFetch(). Данные, пришедшие в структуре SelectListItem, приводятся к строковому формату и помещаются в Grid.

Вот кусок программы, который всем этим занимается (приведен с сокращениями):

 

//------------------------------------------------------------------------------

void __fastcall TmFrm::RunProc(TObject * Sender){

ORA_Statement1->SQLExec();

if ( ORA_Statement1->cCount ){ //Если колонок больше одной.

while (!ORA_Statement1->Eof)){

ResaltGrid->RowCount = j + 1;

ResaltGrid->Cells[0][j] = IntToStr(j);

for (i=0;i<ORA_Statement1->cCount;i++){

sItem = ORA_Statement1->RawFieldGet(i+1);

if ( sItem->uValueRCode != 1405 ){ //Если колонка не NULL

switch ( sItem->uValueType ){

case SQLT_CHR:

case SQLT_AFC:

ResaltGrid->Cells[i+1][j] = "";

ResaltGrid->Cells[i+1][j] = AnsiString((char*)sItem->uValue, sItem->uValueLength);

(ResaltGrid->Cells[i+1][j]).SetLength(sItem->uValueLength);

break;

case SQLT_FLT:

ResaltGrid->Cells[i+1][j] = FloatToStr(*(double*)sItem->uValue);

break;

case SQLT_INT:

ResaltGrid->Cells[i+1][j] = IntToStr(*(long*)sItem->uValue);

break;

case SQLT_DAT:

oDate = (ORA_Date *)sItem->uValue;

wYear = (oDate->cCentury-100)*100+(oDate->cYear-100);

oDateTime1 = EncodeDate(wYear, oDate->cMonth, oDate->cDay);

oDateTime2 = EncodeTime(oDate->cHour-1, oDate->cMinute-1, oDate->cSecond-1, 0);

ResaltGrid->Cells[i+1][j] = oDateTime1.FormatString("dd.mm.yyyy") + " " + oDateTime2.FormatString("hh:nn:ss");

break;

};

};

ORA_Statement1->SQLFetch();

};

 

Посмотрите как происходит преобразование даты из формата ORACLE в формат, понимаемый VCL.

Структура ORA_Date – структура хранения даты/времени в формате ORACLE:

 

typedef struct{

unsigned char cCentury;

unsigned char cYear;

unsigned char cMonth;

unsigned char cDay;

unsigned char cHour;

unsigned char cMinute;

unsigned char cSecond;

}ORA_Date;

 

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

sItem->uValue хранит указатель на сырые данные.

Поле sItem->uValueRCode структуры sItem хранит номер ошибки ORACLE конкретно для этого поля и для этой строки (для каждого конкретного значения). Если sItem->uValueRCode равно 1405, то поле сожержит NULL значение.

sItem->uValueType – хранит тип данных по указателю sItem->uValue.

Свойство Eof экземпляра класса ORA7_Statment ORA_Statement1 содержит флаг конца результирующей выборки. Цикл выборки данных заканчивается когда Eof принимает значение TRUE.

Вот примерный вид экрана программы в работе:

Baind пример для класса ORA7_Satment

Связывание входных переменных с SQL выражением осуществляется при помощи двух методов компоненты ORA7_Statment: SQLDefParam и SQLPassParam. Первый определяет имена, типы и длину входных параметров. Второй осуществляет непосредственное связывание адресов с конкретными значениями параметров с SQL выражением. Конкретные значения передаются в SQLPassParam в виде массива. Примечательно, что в отличие от стандартного механизма VCL передачи параметров данный механизм подразумевает работу с массивами структур. Это значит, что Вы можете одним выражением INSERT положить на сервер сразу несколько строк информации.

Внутри работа метода SQLPassParam основана на OCI вызове obindps.

Методу SQLDefParam передаются два параметра. Первый - есть массив, каждый элемент которого описывает один входной параметр при помощи трех полей структуры DescrParStruct:

 

typedef struct {

AnsiString ParName; //

Char ParType; //'C', 'I', 'F', 'D'

int ParLength; //Only for "C", all athers - 0.

} DescrParStruct;

Поле ParType может содержать символы 'C, I, F, D', что значит 'Символ, целое, действительное, дата'. Вы можете передать параметры только этих типов. Для символьного типа Вы должны определить размер строки, передаваемой на сервер. Имя параметра должно содержать начальное двоеточие – ':Par1'. Параметры связываются только по именам и имеют только один атрибут - IN. (Если хотите большего, напишите это, пожалуйста, сами).

Методу SQLPassParam передаются три параметра. Первый указывает на массив данных, второй указывает на массив, определяющий длину каждого поля и третий есть размер массива структуры.

Для примера я написал небольшой код, который Вам и продемонстрирую в действии.

Вначале необходимо определить таблицу со структурой:

CREATE TABLE VVLAD(

F1 NUMBER(10),

F2 LONG,

F3 VARCHAR2(100),

F4 DATE

)

Потом в среде C++ Builder кидаем на форму два компонента из закладки OCI 73 – ORA7_Connect и ORA7_Statment:

 

Для ORA7_Connect устанавливаем свойства User, Password, Server и делаем коннект активным:

 

Для ORA7_Statment устанавливаем SQL выражение:

'insert into vvlad (f1, f2, f3, f4) values (:ff1, :ff2, :ff3, :ff4)'

И делаем тоже активным.

Далее пропишем реакцию на нажатие кнопки. Программа должна заполнить несколько строк в таблице VVLAD:

 

void __fastcall TForm1::Button1Click(TObject *Sender){

sTemp s1[5];

ssTemp s2[5];

DescrParStruct ds[4];

 

ds[0].ParName = ":ff1";

ds[0].ParType = 'I';

 

ds[1].ParName = ":ff2";

ds[1].ParType = 'C';

ds[1].ParLength = 255;

 

ds[2].ParName = ":ff3";

ds[2].ParType = 'C';

ds[2].ParLength = 50;

 

ds[3].ParName = ":ff4";

ds[3].ParType = 'D';

 

s1[0].f1 = 10;

s1[0].f2 = "Привет для LONG - пункт 1";

s1[0].f3 = "Привет для CHAR - пункт 1";

s1[0].f4 = Date();

s2[0].f1 = 0;

s2[0].f2 = 0;

s2[0].f3 = 0;

s2[0].f4 = 0;

 

s1[1].f1 = 20;

s1[1].f2 = "Привет для LONG - пункт 2";

s1[1].f3 = "Привет для CHAR - пункт 2";

s1[1].f4 = Date();

s2[1].f1 = 0;

s2[1].f2 = 0;

s2[1].f3 = 0;

s2[1].f4 = 0;

 

s1[2].f1 = 30;

s1[2].f2 = "Привет для LONG - пункт 3";

s1[2].f3 = "Привет для CHAR - пункт 3";

s1[2].f4 = Date();

s2[2].f1 = 0;

s2[2].f2 = 0;

s2[2].f3 = 0;

s2[2].f4 = 0;

 

s1[3].f1 = 40;

s1[3].f2 = "Привет для LONG - пункт 4";

s1[3].f3 = "Привет для CHAR - пункт 4";

s1[3].f4 = Date();

s2[3].f1 = 0;

s2[3].f2 = 0;

s2[3].f3 = 0;

s2[3].f4 = 0;

 

s1[4].f1 = 50;

s1[4].f2 = "Привет для LONG - пункт 5";

s1[4].f3 = "Привет для CHAR - пункт 5";

s1[4].f4 = Date();

s2[4].f1 = 0;

s2[4].f2 = 0;

s2[4].f3 = 0;

s2[4].f4 = 0;

 

ORA7_Statement1->SQLDefParam(ds, 4);

ORA7_Statement1->SQLPassParam((void *)&s1[0], (short*)&s2[0], 5);

ORA7_Statement1->SQLExec();

if ( ORA7_Statement1->SQLGetLastError() )

ShowMessage(ORA7_Statement1->SQLDescribeError());

}

 

После нажатия кнопки можно посмотреть, что получилось с таблицей VVLAD любым пригодным для этого средством. Я воспользуюсь программой предыдущего примера:

 

Компонент ORA7_Query

Чтобы было удобно работать в среде C++Builder, необходимо организовать компонент – наследник от стандартного компонента TdataSet. Иными словами – определить свой источник данных для Dbaware компонент. Таких как DBGrid, DBNavigator, DBText, DBEdit и так далее. Тем самым Ваш компонент органично войдет в среду программирования и станет частью библиотеки VCL в части определения источников данных. Имея такой компонент, Вы сможете, бросив его на форму, вместе, допустим, с гридом, получить на экране ‘живые’ данные в процессе проектирования задачи. Технология написания подобных компонент подробно описана в литературе и я не буду останавливать на этом. Отмечу только что это не легко, так как чтобы это только задышало, необходимо определить 23 чисто виртуальных метода, основными среди которых являются методы перемещения по источнику данных, методы извлечения данных в поля. Так же определение полей, поддержка механизма BookMark.

Сами понимаете, что большинство этих задач мной уже решены, при написании компонента ORA7_Statment. Все, что по большому счету остается сделать в наследнике от TdataSet это переопределить 23 метода на соответствующие в компоненте ORA7_Statment.Правда механизм BookMark и определение полей придется все же написать.

Стандартная библиотека VCL предусматривает множество дополнительных возможностей для реализации источников данных. Так, определив виртуальный метод GetBlobStream, Вы тем самым определяете работу с большими объектами. Как-то поля типа LONG и LONG ROW. Написать такой метод для упомянутых полей весьма не просто в концепции модели работы с массивами структур, на чем основаны большинство моих компонент. Это потому, что для извлечения информации из полей LONG и LONG ROW необходимы дополнительные шаги (PieceWise операции), которые не вписываются в общую схему.

Определяя методы FindRecord или Locate, Вы определяете механизм поиска в своем источнике данных нужной записи (как Вы это будете делать– Ваша личная проблема).

И так далее, и тому подобное…

В целом работа с компонентом ORA7_Query (наследником от TDataSet) ни чем не отличается от того, к чему Вы, наверное, уже привыкли. На форму помещается ORA7_Connect. Устанавливается коннект к серверу. Далее помещается ORA7_Statment, который связывается с ORA7_Connect. Устанавливается SQL выражение и компонент переводится в активное состояние. Потом ORA7_Query завязывается с ORA7_Statment, активизируется. И, наконец, стандартный TdataSource и набор DBAware компонент. В качестве одного из них я использую DBGrid.

Вот как выглядит все это в Design Time:

 

 

OCI для ORACLE 8

Введение

В этом разделе я не буду полностью описывать весь OCI для ORACLE 8. Это связано в большей степени с тем, что глобальные концепции работы с объектами ORACLE сервера не изменились по сравнению с OCI ORACLE 7. Изменилась скорее технология. Но изменилась, надо сказать, координально. От прежнего CALL интерфейса не осталось камня на камне. Это видно уже хотя бы потому, что вместо старой DLL библиотеки OCIW32.DLL, размером 17920 байт для поддержки CALL интерфейса была введена новая библиотека ORA803.DLL размером уже (!) 1646592 байта. Именно поэтому больший упор я постараюсь сделать на отличиях OCI 8 от OCI 7.3. Более детальную информацию, естественно, смотрите в документации.

Структура OCI приложения.

Общую структуру OCI приложения для ORACLE 8 можно выразить схемой:

Основное отличие этой схемы от OCI 7.3 Realise - это начальная инициализация и завершение. Это глобальные перемены, я бы даже сказал концептуальные. В OCI ORACLE 8 нет никаких структур LHA, CDA и HDA. Все делается исключительно на Handler-ах.

 

Handler – ы.

Hendler-ы - это структуры данных, которые скрыты от пользователя (от программиста). Ваша программа оперирует только с указателями на них и ей все равно что за структура кроется за тем или иным Handler-ом. В хидер-файлах они определены абстрактным образом:

 

typedef struct OCIEnv OCIEnv; /* environment handle */

typedef struct OCIError OCIError; /* error handle */

typedef struct OCISvcCtx OCISvcCtx; /* service handle */

typedef struct OCIStmt OCIStmt; /* statement handle */

typedef struct OCIBind OCIBind; /* bind handle */

typedef struct OCIDefine OCIDefine; /* Define handle */

typedef struct OCIDescribe OCIDescribe; /* Describe handle */

typedef struct OCIServer OCIServer; /* Server handle */

typedef struct OCISession OCISession; /* Authentication handle */

typedef struct OCIComplexObject OCIComplexObject; /* COR handle */

typedef struct OCITrans OCITrans; /* OCI Transaction handle */

typedef struct OCISecurity OCISecurity; /* OCI Security handle */

 

Как видите их всего 12. Каждый Handler необходим для того или иного вида работы с ORACLE объектами. Но есть Hendler-ы которые являются как бы основополагающими. В их контексте как бы 'живут' остальные. Перед нами вырисовывается иерархическая структура принадлежности Hendler-ов:

 

 

Любой Handler необходимо инициализировать в начале работы и закрывать при окончании работы с ним (кроме Define, Bind и Describe, которые инициализируются и освобождаются автоматически).

Если Вы освобождаете Handler, то Вы освобождаете все зависимые Handler-ы.

На верхней ступени иерархии стоит Environment Handler , в контексте которого живут все остальные, и это первый Handler, который необходимо инициализировать.

Handler-ы инициализируются функцией OCIHandlerAlloc и освобождаются функцией OCIHandlerFree, кроме OCIEnv. Он инициализируется функцией OCIEnvInit. Вызов OCIEnvInit должен быть вторым в любой OCI программе под ORACLE 8, сначала Вы инициализируете среду вызовом OCIInitialize, куда передаете информацию о OCI окружении (THREADER, OBJECT и т.д.) и информацию о функциях резервирующих и освобождающих память.

Далее инициализируются Handler-ы OCITrans, OCIServer, OCISession и OCISvcCtx. Handler OCISvcCtx завязывается на 3 предыдущих специальной функцией OCISetAttr. Это Handler контекста сервиса,и в его задачу входит объединение сервиса для выполнения SQL предложений. Это значит, что Вы можете зарезервировать несколько Handler-ов OCIStmt для нескольких SQL выражений и выполнить их в одном сервисном контексте (что значит и в одном коннекте).

Связь между OCIStmt и OCISvcCtx осуществляется путем непосредственной их передачи в OCI функции обработки.

Любой Handler может включать в себя атрибуты, число и тип которых зависит от типа самого Handler-а. Атрибуты извлекаются и устанавливаются функциями OCIAttrSetи OCIAttrGet. Например, при инициализации Handler-а OCISession ему необходимо передать информацию о пользователе и пароль. Вот приблизительный код как это можно сделать (с Вашего позволения уже на Object Pascal от Delphi 4):

 

{set username attribute in user session handle}

OCIAttrSet( usrhp, //Session handler

OCI_HTYPE_SESSION,

PChar(User),

ub4(Length(User)),

OCI_ATTR_USERNAME,

errhp);

{set password attribute in user session handle}

OCIAttrSet( usrhp, //Session handler

OCI_HTYPE_SESSION,

PChar(Pass),

ub4(Length(Pass)),

OCI_ATTR_PASSWORD,

errhp);

 

Связь между OCIStmt и OCISvcCtx осуществляется путем непосредственной их передачи в OCI функции обработки.

Дескрипторы.

Дескрипторы очень похожи на Handler-ы в том плане, что это тоже ссылки на именованные области памяти никак более для программиста не описанные. Отличия их от Handler-ов в основном в том, что они нужны для принципиально других целей. Их можно сравнить с файловыми Handler-ами. После того, как Вы открыли файл, Вы получаете полный доступ к нему через специальные функции, куда передаете файловый Handler. Точно так же работают и дескрипторы, только описывают они не файлы, а SnapShort-ы, BLOB-ы, ROWID и так далее. И это, на самом деле, очень удобно. Во всяком случае работа с BLOB-ами намного упрощается в сравнении с работой с полями LONG и LONG ROW. Представьте себе, что при операции FETCH в массив структур, когда в результирующей выборке имеются поля BLOB и LONG, LONG вы можете получить только одну порцию, заранее выделенного размера для каждой записи в массиве. BLOB же совсем другое дело. В результирующий массив возвращается дескриптор, который Вы можете обработать в дальнейшем, в частности получить всю информацию с сервера по этому дескриптору (чего не скажешь о LONG-е).

Дескрипторов всего 5:

Основные шаги при написании OCI 8 приложений.

Для выполнения любого SQL предложения в целом требуются следующие шаги:

Рассмотрим эти шаги более подробно.

Инициализация, соединение, создание пользовательской сессии.

Первое, что необходимо, - это инициализировать процесс. Делается это при помощи функции OCIInitilize. В ее параметрах Вы указываете режим существования процесса (параметр mode). Вы можете указать, что Ваша программа многопоточная, что Вы будете использовать объектную опцию. Так же Вы вправе указать режим по умолчанию (не многопоточный, без использования объектной опции). В вызове этой функции Вы так же указываете адреса трех процедур, управляющих резервированием памяти. Если указатели есть NULL значения, то ORACLE использует стандартные функции для Вашей операционной системы.

Второй необходимый шаг - это инициализация Handler-ов. В OCI 8 предусмотрены две специализированные функции – OCIHandleAlloc и OCIHandleFree. Одна инициализирует Handler, вторая его освобождает. Вы должны инициализировать все Handler-ы, за исключением тех, которые инициализируются неявно в других вызовах OCI, например, OCIBindByPos.

Основной Handler OCI среды инициализируется функцией OCIEnvInit().

Третий шаг – это соединение с сервером. Для этой цели есть функция OCIServerAttach(). И прямо противоположная функция OCIServerDetach(). Основным аргументом для этих вызовов служит Server Handler.

Четвертый, заключительный шаг - это создание пользовательской сессии функцией OCISessionBegin. Ей передается Handler пользователя (OCISession), в который уже положены атрибуты (функцией OCIAttrSet), характеризующие имя пользователя и пароль. Кстати, TNS алиас сервера той же функцией заносится в Server Handler. Сессия пользователя завершается по вызову функции OCISessionEnd().

Выполнение SQL предложения.

Основные шаги по выполнению SQL предложений проще всего продемонстрировать следующей схемой:

 

 

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

Commit или Rollback.

Ваше приложение сохраняет изменения в базе данных, сделанные им самим же вызовом OCI процедуры OCITransCommit(). Функция воспринимает в качестве параметра контекст сервера и “закомичивает” все изменения в базе, произошедшие в данном контексте.

Транзакция может быть завершена также автоматически, например, когда Вы выполняете SQL предложение функцией OCIStmtExecute() с параметром mode OCI_COMMIT_ON_SUCCESS.

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

Завершение приложения.

По завершении работы OCI приложение должно выполнить следующие действия:

Обработка ошибок.

Любой OCI вызов возвращает признак ошибки. Этих признаков пять:

Как Вы понимаете, грамотно написанная OCI программа должна подобающим образом реагировать на возвращаемые признаки OCI функций. Для дальнейшей обработки ошибок существует функция OCIErrorGet(), в качестве параметра которая получает Error Handler. Соответственно программа должна вовремя зарезервировать Error Handler для каждого этапа работы, критичного на появление ошибок.

Типы данных.

Помимо типов данных, существующих в ORACLE 7, ORACLE 8 поддерживает новые типы, а с объектной опцией Вы можете делать свои собственные типы.

Новые типы данных:

Кое-какие типы данных Realise 8.0 еще не поддерживает, например, SQLT_CFILE.

Наибольший интерес, естественно, представляют BLOB, CLOB, BFILE и объектный тип SQLT_NTY. Работа с типом данных LOB в ORACLE 8 в корне отличается от работы с полями типа LONG и LONG ROW. Работа с LOB полями ведется через ДЕСКРИПТОРЫ, что намного удобней. На самом деле работать с дескрипторами Вы будете при обработке и полей всех остальных новых типов, но LOB данные – это типы, на которые следует перейти в первую очередь потому, что:

Как Вы понимаете это сильные аргументы.

Как и в ORACLE 7 существуют внутренние и внешние типы данных. Новые типы данных не перекодируются в другие типы при операции FETCH – всегда приходят дескрипторы. Остальное все одинаково.

Общая структура OCI 8 приложения с характерными функциями, и их описание.

 

Функции OCI я буду описывать в терминах языка Object Pascal от Delphi 4 (модуль OCI.pas в приложении к статье)

OCIInitialize().

function OCIInitialize( mode: ub4;

ctxp: Pointer;

malocfp: Tmalocfp;

ralocfp: Tralocfp;

mfreefp: Tmfreefp): sword; cdecl;

Параметры:

Mode Указывает режим инициализации. Возможны значения:

Ctxp указатель на область памяти, по умолчанию передаваемый в функции резервирования памяти.

Malocfp, ralocfp, mfreefp Указатели на функции резервирования памяти.

OCIEnvInit().

function OCIEnvInit( envp: ppOCIEnv;

mode: ub4;

xtramem_sz: size_t;

usrmempp: Pointer): sword; cdecl;

Параметры:

Envp Указатель на Environment Handler.

Mode Режим функционирования среды. Возможны варианты:

xtramem_sz Размер дополнительной памяти для среды.

usrmempp Указатель на указатель для этой памяти.

OCIHandleAlloc().

function OCIHandleAlloc( parenth: Pointer;

hndlpp: pPointer;

type_h: ub4;

xtramem_sz: size_t;

usrmempp: Pointer): sword; cdecl;

Параметры:

Parenth Handler родитель (в основном Environment handler).

Hndlpp Указатель на резервируемый handler.

type_h Тип резервируемого handler-а.

xtramem_sz Размер дополнительной памяти, резервируемой вместе с handler –ом.

usrmempp Указатель на указатель на эту память.

OCIServerAttach().

function OCIServerAttach( srvhp: pOCIServer;

errhp: pOCIError;

dblink: PChar;

dblink_len: sb4;

mode: ub4): sword; cdecl;

Параметры:

Srvhp Указатель на Server handler.

Errhp Error handler

Dblink Указатель на строку, описывающую имя сервера, к которому Вы хотите приконнектиться.

dblink_len Длина предыдущей строки.

mode Для ORACLE 8 должен быть OCI_DEFAULT.

OCISessionBegin().

function OCISessionBegin( svchp: pOCISvcCtx;

errhp: pOCIError;

usrhp: pOCISession;

credt: ub4;

mode: ub4): sword; cdecl;

Параметры:

Svchp Контекст сервиса.

Errhp Error handler.

Usrhp Session handler

Credt Определяет кредит доступа к пользовательской сессии. Если OCI_CRED_RDBMS, то кредит доступа соответствуют пользователю, атрибуты имени и пароля которого присутствуют в OCISession handler.

Mode Определяет различные права доступа пользовательской сессии. Возможны варианты:

    • OCI_DEFAULT
    • OCI_MIGRATE
    • OCI_SYSDBA
    • OCI_SYSOPER
    • OCI_PRELIMAUTH

OCIStmtPrepare().

function OCIStmtPrepare( stmtp: pOCIStmt;

errhp: pOCIError;

stmt: PChar;

stmt_len: ub4;

language: ub4;

mode: ub4): sword; cdecl;

Параметры:

Stmtp Statment handler.

Errhp Error handler.

Stmt Указатель на строку, содержащую SQL выражение.

stmt_len Длина предыдущего параметра.

language Определяет синтаксис SQL выражения (для разных серверов)

mode Только OCI_DEFAULT.

OCIBindByName().

function OCIBindByName( stmtp: pOCIStmt;

bindp: ppOCIBind;

errhp: pOCIError;

placeholder: PChar;

placeh_len: sb4;

valuep: Pointer;

value_sz: sb4;

dty: ub2;

indp: Pointer;

alenp: pub2;

rcodep: pub2;

maxarr_len: ub4;

curelep: pub4;

mode: ub4): sword; cdecl;

Параметры:

Stmtp Statment handler.

Bindp Указатель на Bind Handler, он автоматически инициализируется и уничтожается при уничтожении Statment Handler – а.

Errhp Error handler.

Placeholder Имя переменной связывания.

placeh_len Длина предыдущего параметра.

valuep Адрес связываемого значения или адрес первого элемента массива связываемых значений.

value_sz Размер переменной связывания в байтах. В случае связывания массива максимальный размер массива. Длина элементов массива хранится в параметре alenp.

dty Тип переменной связывания.

indp Указатель на переменную индикатор или массив индикаторов. В них хранятся признаки такие, как, например, NULL значений.

alenp Указатель на массив с действительными размерами связываемых переменных.

rcodep Указатель на массив с кодами возврата для переменной связывания или массива таковых.

maxarr_len Максимально возможное количество элементов связывания типа dty для PL/SQL. Этот параметр не требуется, если Bind осуществляется в SQL.

curelep Указатель на действительное количество элементов для PL/SQL связывания. Для SQL параметр не действительный.

mode Принимает два значения OCI_DEFAULT и OCI_DAT_AT_EXEC. При втором значении функция вычисляет максимальный размер в принципе данных для связывания и возвращает его в параметре value_sz.

OCIStmtExecute().

function OCIStmtExecute( svchp: pOCISvcCtx;

stmtp: pOCIStmt;

errhp: pOCIError;

iters: ub4;

rowoff: ub4;

snap_in: pOCISnapshot;

snap_out: pOCISnapshot;

mode: ub4): sword; cdecl;

Параметры:

Svchp Handler контекста сервиса.

Stmtp Satment Handler.

Errhp Error Handler

iters Количество итераций.

rowoff Начальный индекс, с которого будет начат процесс (Bind или Fetch)

snap_in Необязательный параметр, указывающий на дескриптор SnapShort-а (входного). Сделано это для того, чтобы внутри одного контекста были видныизменения внутри другого контекста без закомичивания данных. Таким образом (через дескрипторы snap_in и snap_out) осуществляется связь между сервисными контекстами.

snap_out Дескриптор SnapShort-а выходного.

mode Может принимать значения:

    • OCI_DEFAULT
    • OCI_DESCRIBE_ONLY
    • OCI_COMMIT_ON_SUCCESS
    • OCI_EXACT_FETCH

Названия режимов говорят сами за себя.

OCIAttrSet().

function OCIAttrSet( trgthndlp: Pointer;

trghndltyp: ub4;

attributep: Pointer;

size: ub4;

attrtype: ub4;

errhp: pOCIError): sword; cdecl;

Параметры:

Trgthndlp Указатель на Handler, чьи атрибуты будут изменены.

Trghndltyp Тип Handler-а.

Attributep Указатель на значение атрибута.

Size Размер значения атрибута.

Attrtype Тип атрибута.

Errhp Error Handler.

OCIAttrGet().

function OCIAttrGet( trgthndlp: Pointer;

trghndltyp: ub4;

attributep: Pointer;

sizep: pub4;

attrtype: ub4;

errhp: pOCIError): sword; cdecl;

Параметры:

Trgthndlp Указатель на Handler, чьи атрибуты будут извлечены.

Trghndltyp Тип Handler-а.

Attributep Указатель на значение атрибута.

Sizep Указатель на размер значения атрибута.

Attrtype Тип атрибута.

Errhp Error Handler.

OCIDefineByPos().

function OCIDefineByPos( stmtp: pOCIStmt;

defnp: ppOCIDefine;

errhp: pOCIError;

position: ub4;

valuep: Pointer;

value_sz: sb4;

dty: ub2;

indp: Pointer;

rlenp: pub2;

rcodep: pub2;

mode: ub4): sword; cdecl;

Параметры:

Stmtp Statment Handler.

Defnp Указатель на Define Handler.

Errhp Error handler.

Position Позиция колонки (select list item).

Valuep Адрес, куда отправлять значение select list item при операции FETCH. Или указатель на массив таких значений.

value_sz Размер значения или размерность массива значений.

dty Тип значения.

indp Указатель на переменную индикатор или массив таких переменных.

rlenp Указатель на переменную с длиной значения или массив длин таких значений.

rcodep Указатель на переменную возвращаемого значения или массив таких значений.

mode Может быть OCI_DEFAULT или OCI_DYNAMIC_FETCH. Во втором случае происходит автоматическое выделение памяти под выходные значения при помощи специальной CALLBACK функции (Вы ее пишите сами). Адрес CALLBACK функции указывается при вызове функции OCIDefineDynamic.

OCIStmtFetch().

function OCIStmtFetch( stmtp: pOCIStmt;

errhp: pOCIError;

nrows: ub4;

orientation: ub2;

mode: ub4): sword; cdecl;

Параметры:

Stmtp Statment Handler.

Errhp Error Handler.

Nrows Количество строк для операции.

Orientation Направление. Для Realise 8.0 доступен (пака) только один OCI_FETCH_NEXT.

Mode Передавайте OCI_DEFAULT, не ошибетесь.

OCISessionEnd().

function OCISessionEnd( svchp: pOCISvcCtx;

errhp: pOCIError;

usrhp: pOCISession;

mode: ub4): sword; cdecl;

Параметры:

Svchp Handler сервисного контекста.

Errhp Error Handler.

Usrhp Session handler.

Mode Передавайте OCI_DEFAULT, не ошибетесь.

OCIServerDetach().

function OCIServerDetach( srvhp: pOCIServer;

errhp: pOCIError;

mode: ub4): sword; cdecl;

Параметры:

Srvhp Handler Server.

Errhp Error Handler.

Mode Передавайте OCI_DEFAULT, не ошибетесь.

OCIHandleFree().

function OCIHandleFree(hndlp: Pointer; type_h: ub4): sword; cdecl;

Параметры:

Hndlp Указатель на Handler.

type_h Тип Handler –а.

Реализация OCI для ORACLE 8 в различных системах программирования.

Связь с ORACLE 8 сервером через OCI в Borland Delphi 4

Модуль в Delphi 4, описывающий OCI – OCI.PAS

В связи с тем, что CALL интерфейс поставляется в терминах языка С, то все его определения приходится переписывать в терминах других языков, если Вы работаете на них. Delphi 4 не исключение. Мной были переписаны все #define определения из Header файлов, описывающих OCI 8 для С: OCI.H, OCIAP.H, OCIDEF.H, ORATYPES.H, OCIDFN.H и т.д. И были переведены прототипы всех функций (так называемых Relational) на Object Pascal от Delphi 4 для ORACLE 8 (библиотека ORA803.DLL) и ORACLE 7 (библиотека OCIW32.DLL).

В самом файле OCI.PAS более чем 2000 строк, можете представить себе объем работы.

Вы можете свободно пользоваться всеми материалами, предоставляемыми мной вместе со статьей, в том числе и файлом OCI.PAS.

Пример выполнения любых SQL выражений, построенный исключительно на OCI 8.

Пример находится в директории …/OCI/Borland Delphi 4/Test1. Работа примера построена на классической схеме выполнения SQL предложений, приведенной ранее.

Чисто визуально работает это следующим образом:

Перед Вами форма с несколькими кнопками. Вы нажимаете кнопку ‘OCI 8 Test’ и получаете диалоговое окошко идентификации пользователя. Если соединение прошло успешно, то Вам предоставляется возможность в простом редакторе отредактировать SQL предложение. После выхода из редактора программа пытается выполнить SQL предложение и, если это запрос, то заполняет его результатами StringGrig, который и показывает в завершении работы. Все просто. Заглянем внутрь.

OCIInitialize( OCI_THREADED,

nil,

nil,

nil,

nil);

 

OCIEnvInit( @envp,

OCI_DEFAULT,

0,

nil);

 

Вначале инициализируем среду и Environment Handler.

 

OCIHandleAlloc( envp,

@srvhp,

OCI_HTYPE_SERVER,

0,

nil);

OCIHandleAlloc( envp,

@errhp,

OCI_HTYPE_ERROR,

0,

nil);

 

Инициализация Server Handler и Error Handler.

 

I := OCIServerAttach( srvhp,

errhp,

PChar(Trim(oAut.eServer.Text)),

length(Trim(oAut.eServer.Text)),

OCI_DEFAULT);

 

Конект к серверу.

 

if i = 0 then

begin

ShowMessage('Server attach successfull!');

i := OCIHandleAlloc( envp,

@usrhp,

OCI_HTYPE_SESSION,

0,

nil);

if i = 0 then

begin

i := OCIHandleAlloc( envp,

@svchp,

OCI_HTYPE_SVCCTX,

0,

nil);

 

Инициализация Session Handler и Сервис контекст Handler.

 

if i = 0 then

begin

OCIAttrSet( svchp,

OCI_HTYPE_SVCCTX,

srvhp,

ub4(0),

OCI_ATTR_SERVER,

errhp);

{set the server context in the service context}

OCIAttrSet( usrhp,

OCI_HTYPE_SESSION,

PChar(Trim(oAut.eUser.Text)),

Length(Trim(oAut.eUser.Text)), OCI_ATTR_USERNAME,

errhp);

{set username attribute in user session handle}

OCIAttrSet( usrhp,

OCI_HTYPE_SESSION,

PChar(Trim(oAut.ePass.Text)),

length(Trim(oAut.ePass.Text)),

OCI_ATTR_PASSWORD,

errhp);

{set password attribute in user session handle}

 

Установка атрибутов в Handler-ы.

 

 

i := OCISessionBegin( svchp,

errhp,

usrhp,

OCI_CRED_RDBMS,

OCI_DEFAULT);

if i = 0 then

begin

OCIAttrSet( svchp,

OCI_HTYPE_SVCCTX,

usrhp,

ub4(0),

OCI_ATTR_SESSION,

errhp);

{ set the user session in the service context }

 

Старт пользовательской сессии.

 

OCIHandleAlloc( envp,

@stmthp,

OCI_HTYPE_STMT,

0,

nil);

 

Инициализация Statment Handler-а.

 

PrepareStatus := false;

if OCIStmtPrepare( stmthp,

errhp,

PChar(cSQL),

Length(cSQL),

OCI_NTV_SYNTAX,

OCI_DEFAULT) = OCI_SUCCESS then

begin

OCIStmtExecute(svchp,

stmthp,

errhp,

1,

0,

nil,

nil,

OCI_DESCRIBE_ONLY);

PrepareStatus := true;

end;

 

Подготовка к выполнению.

 

if PrepareStatus then

begin

ShowMessage('Prepare was successfull!');

ListBuf := TList.Create;

LstColDsc := TList.Create;

OCIAttrGet( stmthp,

OCI_HTYPE_STMT,

@ColumnCount,

nil,

OCI_ATTR_PARAM_COUNT,

errhp);

 

Получаем количество колонок.

 

for position := 1 to ColumnCount do

begin

OCIParamGet(stmthp, OCI_HTYPE_STMT, errhp, @colhd, position);

 

Получаем дескриптор колонки.

 

j := LstColDsc.Add(pColDesc(StrAlloc(sizeof(ColDesc))));

aCol := pColDesc(LstColDsc.Items[j]);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.MaxSize,

nil,

OCI_ATTR_DATA_SIZE,

errhp);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.InernalDatType,

nil,

OCI_ATTR_DATA_TYPE,

errhp);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@cc,

@aCol.LengthOfColumnName,

OCI_ATTR_NAME,

errhp);

move(cc^, aCol.ColumnName, aCol.LengthOfColumnName);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.MaxDisplaySize,

nil,

OCI_ATTR_DISP_SIZE,

errhp);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.PrecOfNum,

nil,

OCI_ATTR_PRECISION,

errhp);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.ScalOfNum,

nil,

OCI_ATTR_SCALE,

errhp);

OCIAttrGet( colhd,

OCI_DTYPE_PARAM,

@aCol.NullOk,

nil,

OCI_ATTR_IS_NULL,

errhp);

 

И вытаскиваем из этой колонки кучу атрибутов, полностью ее характеризующих.

 

j := ListBuf.Add(Pointer(StrAlloc(sizeof(RecResult))));

pRecResult(ListBuf.Items[j]).ResStr := StrAlloc(aCol.MaxDisplaySize);

OCIDefineByPos( stmthp,

@defhp,

errhp,

position,

Pointer(pRecResult(ListBuf.Items[j]).ResStr),

aCol.MaxDisplaySize,

SQLT_CHR,

@pRecResult(ListBuf.Items[j]).indp,

@pRecResult(ListBuf.Items[j]).rlen,

@pRecResult(ListBuf.Items[j]).rcode,

OCI_DEFAULT);

 

Определение адресов для выходных переменных. В данном случае эти адреса распределяются динамически при помощи динамического списка ListBuf.

 

FormResult.StringGrid1.Cells[aCol.pos, 0] := aCol.ColumnName;

 

Заполнение заголовков для StringGrig.

 

end;

OCIStmtExecute( svchp,

stmthp,

errhp,

0,

0,

nil,

nil,

OCI_DEFAULT);

 

Выполнение SQL предложения.

 

repeat

i := OCIStmtFetch( stmthp,

errhp,

1,

OCI_FETCH_NEXT,

OCI_DEFAULT);

 

Получить одну запись из выборки.

 

if i = 0 then

begin

FormResult.StringGrid1.RowCount :=

FormResult.StringGrid1.RowCount + 1;

 

Нарастим StringGrig.

 

for k := 0 to ListBuf.Count - 1 do

begin

tStr := String(pRecResult(ListBuf.Items[k]).ResStr);

 

TStr – строка для поля k из выборки текущей строки.

 

if pRecResult(ListBuf.Items[k]).indp = -1 then

 

Если переменная индикатор указывает, что значение есть NULL, то поместить в грид пустую строку.

 

FormResult.StringGrid1.Cells[k+1, RowCnt] := ''

else

FormResult.StringGrid1.Cells[k+1, RowCnt] :=

copy( tStr,

1,

pRecResult(ListBuf.Items[k]).rlen);

 

В противном случае поместить в грид строку.

 

end;

end;

until i <> 0;

 

В конце процедуры освобождение Handler-ов, закрытие пользовательской сессии и дисконнект.

 

OCIHandleFree( stmthp,

OCI_HTYPE_STMT);

end;

OCISessionEnd( svchp,

errhp,

usrhp,

OCI_DEFAULT);

end

else ShowMessage('Session was not started!');

OCIHandleFree( svchp,

OCI_HTYPE_SVCCTX);

OCIHandleFree( usrhp, OCI_HTYPE_SESSION);

end;

end;

OCIServerDetach( srvhp,

errhp,

OCI_DEFAULT);

OCIHandleFree(errhp, OCI_HTYPE_ERROR);

end;

OCIHandleFree(srvhp, OCI_HTYPE_SERVER);

end;

end;

 

Набор компонент для Delphi 4 для прямой работы с ORACLE 8 сервером.

Работая в такой среде, как Delphi 4, было бы странно писать каждый раз код наподобие предыдущего примера для каждой своей задачи. Имея библиотеку визуальных компонент, весьма естественно расширить ее возможности для прямой работы с объектами ORACLE сервера. Набор таких компонент был мной разработан и оттестирован. Это ни есть предел совершенства, но работает вполне приемлемо и быстро. В компонентах нет некоторых возможностей, но, реализовав их, я бы сделал коммерческий продукт, что не является моей конечной целью. По логике любого OCI приложения я выделил несколько отдельных классов, отвечающих каждый за свое. To8Env – отвечает за среду существования OCI приложения и за связь с сервером. To8Session– организация пользовательской сессии внутри Environment. To8Stmt – чисто виртуальный компонент, поставляющий интерфейс для объекта TdataSet (точнее для его наследников в моих компонентах). To8StmtOrd – конкретный наследник от To8Stmt, реализующий выполнение простых SQL выражений (простых – это значит совместимых с ORACLE 7 по типам данных + LOB - ы). Так же он отвечает за перемещение по выборке данных и поставляет эти данные. To8DataSet – наследник от стандартного класса TdataSet. Является источником данных для всех DBAware компонент Delphi 4.

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

Партнерские отношения между компонентами не просто описывать в концепции VCL библиотеки, особенно если такие компоненты составляют целые цепочки (именно так в моем случае). Положение усугубляется еще и тем, что нужно учитывать дополнительные свойства (активен партнер или не активен) при любом изменении в схеме конечного приложения. Имеется так же разница между поведением компонент в Design time и Run Time. В общем, я хочу сказать, что проектирование набора компонент для VCL библиотеки - само по себе занятие не для слабонервных и требует отдельного описания. Так, я, например, не сразу дошел, как инициализировать свойства компонент, когда важен порядок инициализации (из DFM файла они приходят в произвольном порядке, невзирая на утверждение в документации). Например, Вы не можете установить Active в TRUE объекта To8Session, если Вы еще не установили для этого объекта партнерское отношение с одним из объектов To8Env или этот To8Env сам не активен. Решение пришло ’само собой’, когда я заглянул в исходные тексты VCL и посмотрел, как они это решили. Они это решили при помощи еще одной protect переменной, которую заполняли значением из DFM файла на стадии загрузки, и далее переписывали метод Loaded, в котором инициализировали свойства в том порядке, в котором необходимо для данного компонента. Партнерские взаимоотношения между компонентами требуют тщательного программирования. Нужно предусматривать ситуации, когда пользователь выстраивает на форме партнерские цепочки компонент, а потом берет и удаляет компонент с формы кнопочкой Del из середины этой цепочки. Представьте себе, сколько смеху было при работе с первыми версиями компонент, где я еще не знал, как предусмотреть подобные ситуации. Сейчас я это решил путем хранения динамического списка всех внешних партнеров в компоненте. При изменении свойств компонента он оповещает всех своих партнеров, которые реагируют соответствующим образом.

Вы видите, что помимо собственно функциональной части – работы с объектами базы данных ORACLE 8, приходится заниматься еще кучей всяких тонкостей. Но результат, откровенно говоря, впечатляет. И в конце работы чувствуешь, что потратил время не даром. В этом плане библиотека VCL и среда разработки очень сильные вещи. Честно говоря, я не встречал еще продуктов по своей мощи хотя бы приближающихся к средствам разработки приложений от компании Inprise. Единственное, что меня удручает это BDE. Именно поэтому я и взялся за данную работу.

Но вернемся к компонентам прямого доступа к ORACLE. Со стороны пользователя работать с ними очень просто. Киньте на форму компонент To8Env, установите свойство Server в TNS алиас сервера, с которым Вы хотите связаться (это просто строка) и установите соединение путем перевода Active в TRUE. Если свойство Active не устанавливается, то среда выдаст сообщение об ошибке, почему это сделать не возможно. Следующий компонент, который Вы должны поместить на форму, это компонент To8Session. Ему Вы устанавливаете партнера To8Env, указываете имя пользователя и пароль и тоже переводите в активное состояние свойством Active. У одного To8Env может быть несколько пользовательских сессий (несколько партнеровTo8Session). Далее для выполнения чего-либо значимого на сервере, на форме Вам необходим компонент To8StmtOrd. Его Вы привязываете к какой - либо пользовательской сессии и устанавливаете для него SQLпредложение, требуемое для выполнения. Так же, как все остальные, переводите его в активное состояние. Он может не перевестись в активное состояние либо по причинам ошибок, либо из-за того, что это SQL выражение есть не запрос. Если SQL в To8StmtOrd запрос и компонент активен, результаты запроса могут быть выведены в какой-нибудь стандартный Control, который DBAware. Для этого положите на форму To8DataSet, свяжите его с To8StmtOrd, активизируйте, положите на форму TdataSource и, допустим, TDBGrid. Свяжите эти компоненты стандартным образом. И Вы тут же получите на форме ‘живые’ данные.

С одной пользовательской сессией Вы можете связать несколько компонент To8StmtOrd, причем OCI сам будет следить за распределением ресурсов между Stmt Handler-ами в многопоточном приложении (в вызове OCIEnvInit параметр mode OCI_DEFAULT).

На один объект To8StmtOrd Вы можете повесить только один источник данных To8DataSet для DBAware Control-ов.

При проектировании формы с компонентами прямого доступа к серверу ORACLE 8 очень важен порядок инициализации этих компонент на форме. Он такой: To8Env, To8Session, To8StmtOrd, To8DataSet. Другой порядок может возникнуть, когда Вы ‘активно’ поработали с формой, т.е. чего-то удалили, чего-то опять восстановили. В принципе, это не сильно страшно и к ошибкам не приведет (только к Вашим собственным). Просто не смогут быть установлены свойства Active некоторых компонент, а это несомненно приведет к нежелательным последствиям. Все это я говорю к тому, что после любого редактирования формы обязательно проверяйте последовательность инициализации компонент на ней (правая кнопка мыши на форме->Creation Order). Это особенно актуально для длинных цепочек партнерских отношений.

Родственные и партнерские отношения хорошо демонстрирует следующая схема:

 

 

А вот так может выглядеть форма в процессе разработки приложения:

 

 

Теперь я попробую кратко описать каждый компонент в отдельности.

Компонет To8Env

Описание компонента выглядит следующим образом (только public и published секции):

 

To8Env = class(TComponent)

……

public

{ Public declarations }

 

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

 

…….

 

property hEnv: pOCIEnv read envhp;

property hServ: pOCIServer read srvhp;

property hErr: pOCIError read errhp;

property ErrMes: String read GetErrMessage;

property ErrNum: sb4 read GetErrCode;

 

published

{ Published declarations }

 

property Server: String read Srv write SetSrv;

property Active: Boolean read GetActive write SetActive;

 

end;

 

Как видите, в published секции присутствуют только два свойства Server и Active. В первое помещается имя сервера, а второе переводит компонент в активное состояние.

Свойства в public секции определяют интерфейс к внутренним handler-ам компонента для компонентов партнеров.

Свойство ErrMes возвращает строку с последней ошибкой.

Свойство ErrNum возвращает код последней ошибки. Чаще всего он совпадает с кодом ошибки ORACLE.

Компонент To8Session

Вот сокращенное описание компонента To8Session:

 

To8Session = class(TComponent)

….

public

{ Public declarations }

 

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

 

property hSess: pOCISession read usrhp;

property hSvc: pOCISvcCtx read svchp;

property hErr: pOCIError read errhp;

property ErrMes: String read GetErrMessage;

property ErrNum: sb4 read GetErrCode;

 

………

 

published

{ Published declarations }

 

property Environment: To8Env read FEnv write SetoEnv;

property User: String read sUser write sUser;

property Pass: String read sPass write sPass;

property Active: boolean read GetActive write SetActive;

 

end;

 

Свойство Environment устанавливает партнерские отношения с компонентом To8Env.

User и Pass описывают соответственно пользователя и пароль.

Active – переводит компонент в активное состояние.

Свойства в public секции определяют интерфейс к внутренним handler-ам компонента для компонентов партнеров.

Свойство ErrMes возвращает строку с последней ошибкой.

Свойство ErrNum возвращает код последней ошибки. Чаще всего он совпадает с кодом ошибки ORACLE.

Компонет To8Stmt

Это чисто виртуальный класс, который нельзя на прямую использовать в приложении. Нужен он только для одной цели: стандартизации интерфейса между компонентами, возвращающими Result Set, написанными с использованием CALL интерфейса, и компонентом, наследником от TdataSet. Обратите внимание на то, что наследник от TdataSet один – To8DataSet, но прицепить к нему можно множество компонент, возвращающих Result Set, построенных на разных принципах работы, но имеющих один интерфейс, благодаря наследованию от To8Stmt. В данной работе реализован только один наследник от To8Stmt - To8StmtOrd,и еще один в планах - To8Table

Сокращенное описание To8Stmt:

 

To8Stmt = class(TComponent)

……

protected

…..

function GetRawBuffer: PChar; virtual; abstract;

function GetRawInd: psb2; virtual; abstract;

function GetRawRCode: pub2; virtual; abstract;

function GetRawRlen: pub2; virtual; abstract;

…..

public

{ Public declarations }

constructor Create(AOwner: TComponent); override;

destructor Destroy; override;

 

procedure Open; virtual;

procedure Close; virtual;

procedure Prepare; virtual; abstract;

procedure ExecSql; virtual; abstract;

procedure MoveTo(NewRec: ub4); virtual; abstract;

procedure MoveBy(SkipRec: sb4); virtual; abstract;

procedure First; virtual; abstract;

procedure Next; virtual; abstract;

procedure Prior; virtual; abstract;

procedure Last; virtual; abstract;

function RawFieldGet(position: sword): pSelectListItem; virtual; abstract;

function RawFieldStructGet(position: sword): pRStruct; virtual; abstract;

function GetBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; virtual; abstract;

property hStmt: pOCIStmt read stmthp;

property hErr: pOCIError read errhp;

property ErrMes: String read GetErrMessage;

property ErrNum: sb4 read GetErrCode;

 

property FieldCount: ub4 read FFieldCount;

property RecordSize: ub2 read FRecordSize;

 

property Eof: boolean read Feof;

property Bof: boolean read Fbof;

 

property RecNo: ub4 read FRecno write MoveTo;

 

property FieldName[Index: Integer]: String read GetFieldName;

property RawFieldStruct[position: sword]: pRStruct read RawFieldStructGet;

property RawField[position: sword]: pSelectListItem read RawFieldGet;

property RawBufferGet: PChar read GetRawBuffer;

property RawIndGet: psb2 read GetRawInd;

property RawRCodeGet: pub2 read GetRawRCode;

property RawRlenGet: pub2 read GetRawRlen;

 

property Active: boolean read GetActive write SetActive;

property oraSession: To8Session read GetSession write SetSession;

 

end;

 

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

GetRawBuffer – возвращает буфер текущей строки.

GetRawInd – возвращает массив переменных индикаторов для текущей строки.

GetRawRCode – возвращает массив кодов возврата для каждого поля текущей строки.

GetRawRlen – возвращает массив длин полей для текущей строки (актуально для строковых полей).

 

Open

Close

 

Открывают и закрывают источник данных.

 

MoveTo

MoveBy

First

Next

Prior

Last

 

Абстрактные методы навигации по данным.

 

Prepare

ExecSql

 

Подготавливают и выполняют SQL предложение (если таковое имеется).

 

RawFieldGet(position: sword): pSelectListItem; - Возвращает значение поля в ‘сыром’ виде – в виде структуры SelectListItem по номеру этого поля.

RawFieldStructGet(position: sword): pRStruct; - Возвращает ‘сырое’ описание поля в виде структуры Rstruct по номеру этого поля.

GetBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; - при определении этого метода в классах потомках TdataSet начинает понимать поля типа LONG и LOB.

 

Свойства:

 

HStmt – Statment Handler.

 

HErr – Error Handler.

ErrMes – Строка с последним сообщением об ошибке.

ErrNum – Номер последний ошибки.

 

FieldCount – Количество полей.

RecordSize – Длина строки.

 

Eof – Флаг достижения источником данных конца.

Bof – Флаг, показывающий, что была попытка перемещения в источнике данных за первую запись.

 

RecNo – Номер записи.

 

FieldName[Index: Integer] – Имя поля по номеру поля.

 

Active – Флаг активного состояния.

OraSession – Сессия, к которой привязывается компонент.

 

Компонет To8StmtOrd

Поскольку компонент To8StmtOrd является прямым наследником To8Stmt то методы и свойства в нем определенные, есть конкретная реализация абстрактных методов To8Stmt. По этой причине я не буду подробно их расписывать, но если Вам интересно, то загляните в файл o8StmtOrd.pas и посмотрите его описание.

Вместо этого я попытаюсь рассказать, что это такое и как оно работает.

Компонент нужен для выполнения любых SQL выражений. Если SQL выражение – запрос, компонент предоставляет сервис для перемещения по результирующей выборке. Делает он это, в отличие от всех компонентов, которые описаны ранее, совершенно другим способом. Как выяснилось, значительно более верным и надежным.

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

Но, правда, дольше.

В остальном, компонент To8StmtOrd полный аналог классов, сделанных мною ранее.

Компонет To8DataSet

Основная сложность при написании компонента, наследника от TdataSet, заключается в изучении механизма VCL библиотеки, заложенного разработчиками для классов, поставляющих наборы данных. Механизм, надо сказать, весьма причудлив с первого взгляда. Но когда привыкаешь, кажется, что лучше и не придумаешь.

В принципе, построение своих собственных наборов данных достаточно хорошо описано в литературе по DELPHI и по VCL и я не буду останавливаться на этом очень подробно.

В своем наследнике от TdataSet Вам предлагается написать 23 метода для Delphi 3 и 22 для Delphi 4. В зависимости от того, как Вы их напишите, Вы и будете работать.

Ничего сложного там нет: вначале определяете поля, затем механизм резервирования буферов записей, извлечение этих записей, механизм BookMark-ов. И, если все правильно, то все сразу начинает работать в стандартной для DELPHI среде разработки. Очень удобно.

Но, правда, для полного счастья не хватает механизма Cashed Updates , поиска по выборке и т.д., и т.п. Список, как выясняется, почти бесконечный.

Пример с набором компонент для Delphi 4 для прямой работы с ORACLE 8 сервером.

Пример представляет окно, разделенное сплитеров по горизонтали. В верхней части Вы вбиваете SQL выражение, в нижней - получаете результат.

Вот как выглядит это окно в Design Time-е:

 

 

Как видите, на форме присутствует полный набор компонент для прямой работы с ORACLE сервером и два DBAware компонента – DBGrid и DBNavigator. Оба компонента прицеплены к одному и тому же TdataSet-у – o8DataSet1:To8DataSet.

Вы можете в любой момент приконнектится к любому серверу, набрать SQL предложение и выполнить его.

Вот реакция на клавишу Run:

 

procedure TmFm.Run;

begin

//Переводим компонент To8StmtOrd в неактивное состояние.

o8StmtOrd1.Active := false;

//Заменяем SQL выражение.

o8StmtOrd1.SQL := Stmt_Text.Lines;

//Снова активизируем. После этого включаются DBAware компоненты.

o8StmtOrd1.Active := true;

//Обработка ошибок.

if o8StmtOrd1.Active then o8DataSet1.Active := true else

begin

if o8StmtOrd1.ErrNum = 0 then

ShowMessage('SQL предложение выполнено успешно!')

еlse

ShowMessage(o8StmtOrd1.ErrMes);

end;

end;

 

Вот пример в работе:

 

 

Заключение

OCI – наиболее мощный, наиболее полный, наиболее быстрый, и вместе с тем самый нудный инструмент для манипулирования объектами сервера ORACLE.

Сайт создан в системе uCoz