Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

LINTER от 04.02.15

.pdf
Скачиваний:
48
Добавлен:
21.05.2015
Размер:
2.16 Mб
Скачать

Практическое занятие 12

Пример разработки приложения для ЛИНТЕР с использованием Qt 91

 

источник данных имеет 3 сообщения:

 

onError - генерируется при возникновении ошибки,

 

onFullChange - генерируется при изменении текущего состояния,

 

onCurRowChange - генерируется при изменении текущей строки курсора

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

CSqlDS – класс, который делегирует свою функциональность по работе с выборкой по SQL-запросу. Класс имеет одно сообщение 'onError' (в принципе можно обойтись и без него, если построить контроль ошибок через возвращаемый результат или исключения). В классе CSsqlDS инкапсулируется большинство работы нашего примера с СУБД ЛИНТЕР. Используются обычные вызовы Call-интерфейса, уже изученные нами.

Для связи элементов графического интерфейса с данными из БД необходимо модифицировать стандартные элементы управления так, чтобы они работали через

CDataSource.

Однострочный элемент должен выглядеть примерно так:

class CMyEdit : public QLineEdit

{

...

public:

void setDS(CDataSource *ds);

void setField(int f) {m_field = f;} public slots:

void onDsError(long); void onDsFullChange();

void onCurRowChange(long oldrow);

...

};

Для многострочного элемента необходимы (на примере с 'QListBox') а) Функции конвертации номера строки в базе и в элементе:

long rowToDbRow(int); int dbRowToRow(long);

б) Синхронизация изменения положения курсора в источнике данных при изменении строки в управляющем элементе. Для этого подписываемся на сигнал:

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

Практическое занятие 12

92 Пример разработки приложения для ЛИНТЕР с использованием Qt

connect(this, SIGNAL(highlighted(int)), this, SLOT(slot_highlighted(int)));

и пишем следующий код:

void CMyListBox::slot_highlighted(int)

{

m_ds->setCurRow(rowToDbRow(currentItem()));

}

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

Указания к практической работе:

1.Просмотрите исходные тексты 1-го этапа (директория step1). В директории datasource находятся h-файлы с описанием интерфейсных классов и cpp-файлы с реализацией классов источника данных. В директории ctrl находятся описания и реализация классов визуальных элементов управления, расширенных до возможности работы с источниками данных. Кроме того, там описан и реализован класс формы нашего тестового приложения. В файле main.cpp содержится код основной программы, которая инициализирует приложение, создает соединение с БД ЛИНТЕР (используются жестко закодированные имя и пароль “SYSTEM/MANAGER”), инициализирует источник данных и создает графическую форму. Обратите внимание на makefile. Там необходимо переопределить переменные LINTER (на путь, в котором установлена СУБД ЛИНТЕР) и QTDIR (путь к установке Qt).

20.Скомпилируйте исполняемый файл (команда make). Обратите внимание, что программа компилируется с отладочной информацией (ключ –g).

21.Запустите программу и просмотрите содержимое таблицы AUTO в форме приложения.

22.Запустите программу в отладчике gdb. Поставьте точки останова в методах, реализующих работу источника данных с СУБД ЛИНТЕР, например в методах

CSqlDS::retrieve и CSqlDS::getField. Понаблюдайте за исполнением методов,

включая вызовы ЛИНТЕР, по шагам.

12.2. Второй этап

Добавим в наш пример возможность обработки сообщений от СУБД ЛИНТЕР. Для приложения интересны асинхронный прием сообщения и синхронная обработка. Асинхронная обработка имеет ряд системных ограничений и не все вызовы из нее можно делать (в POSIX стандарте есть список разрешенных вызовов).

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

Практическое занятие 12

Пример разработки приложения для ЛИНТЕР с использованием Qt 93

Разработаем по нашей проблеме класс AsyncManager Наш класс содержит три статические функции:

 

AsyncManager::addNewAsync - регистрация синхронного обработчика

 

для асинхронного запроса по определенному каналу;

 

AsyncManager::removeAsync - снятие регистрации синхронного

 

обработчика для асинхронного запроса;

 

AsyncManager::onAsyncWork - универсальный асинхронный обработчик,

 

передается как аргумент вызова inter.

Для синхронизации используем системный (Qt-1.4.x) цикл обработки сообщений. В таком цикле обязательно стоит вызов типа 'select(.....)', который проверяет состояние по указанным дескрипторам ввода-вывода, в Qt есть класс QSocketNotifier, который позволяет зарегистрировать дескриптор ввода-вывода на такую проверку. Мы воспользуемся им. Создадим 'pipe' и будем его использовать как флаг наличия синхронного обработчика (для асинхронного запроса) в очереди. Для этого переводим 'pipe' в режим неблокирующей работы и регистрируем дескриптор чтения (на прослушивание в 'select') таким образом:

m_qNotifier = new QSocketNotifier(m_pipe[0], QSocketNotifier::Read, this);

connect(m_qNotifier, SIGNAL(activated(int)), this, SLOT(SyncProc(int)));

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

AsyncManager::onAsyncWork производится перевод из m_targetList в m_raiseList список.

Для активизации 'QSocketNotifier' в 'pipe' записывается 1 байт таким образом:

while(write(m_pipe[1], &event, sizeof(event)) < 0) if (errno != EINTR)

{

errno = 0; break;

}

Цикл 'while' здесь применяется для гарантированной записи в 'pipe'. Если ‘pipe’ уже заполнен, то запись пропускается (т.к. любой системный вызов может быть прерван обработкой по сигналу, см. POSIX).

Все функции класса, которые модифицируют списки m_targetList и m_raiseList при своей работе, должны блокировать асинхронную обработку т.к. AsyncManager::onAsyncWork также модифицируют эти списки. Блокировать необходимо сигналы SIGIO и SIGUSR1 - эти сигналы могут быть использованы для асинхронной обработки на клиентской части СУБД ЛИНТЕР.

Универсальный синхронный обработчик AsyncManager::SyncProc вызывается из цикла обработки очереди сообщений при активизации класса QSocketNotifier. В нем из

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

Практическое занятие 12

94 Пример разработки приложения для ЛИНТЕР с использованием Qt

списка m_raiseList активизируется синхронные обработчики по каждому каналу. Список m_raiseList и 'pipe' очищается. Цикл ‘while(read(m_pipe[0], &event, sizeof(event)) > 0)’

применяется для гарантированной очистки 'pipe', как и в случае с write.

В приложении создается один объект класса AsyncManager, его конструктор содержит код инициализации - создание 'pipe' и QSocketNotifier.

Класс CDbEvent непосредственно связывается с ожидаемым событием Линтера, в его конструктор передается имя события. Активизация события в программе осуществляется через метод-сигнал onRaise, интересующиеся объекты должны подписаться на этот сигнал (здесь говорится о сигнале, как о термине Qt).

Вся работа по обработки асинхронного запроса производится через функциональность выше описанного класса, синхронный обработчик - статическая функция класса CDbEvent::onSelect. Объект может находиться в 3-х состояниях: Empty/Busy/Ready. В состояние Busy (ожидания события) объект переводится вызовом метода retrieve, т.е. - асинхронно выполняется запрос

wait event auto_change;

Внутри метода retrieve открывается новый курсор, чтобы освободить соединение для другой работы. Метод clear выполняет запрос

clear event auto_change;

Метод destroy переводит объект в состояние 'Empty'.

Указания к практической работе:

1.Просмотрите исходные тексты 2-го этапа (директория step2). По сравнению с первым этапом, во-первых, добавлена директория async, в которой находится реализация универсального менеджера асинхронной обработки. Во-вторых, в директории datasource добавлены исходные тексты класса CDbEvent. В файле main.cpp добавлена инициализация события.

23.Скомпилируйте исполняемый файл (команда make).

24.Запустите программу и просмотрите содержимое таблицы AUTO в форме приложения. Затем измените содержимое таблицы AUTO из другой сессии, например, подав запрос из программы INL. Приложение должно автоматически отобразить изменения.

25.Запустите программу в отладчике gdb. Посмотрите по шагам, как в программе выполняется обработка события БД.

12.3. Третий этап

На этом этапе мы добавим асинхронную обработку запросов в классе CSqlDS.

В интерфейсе класса CDataSource никаких изменений нет, но объект теперь может находиться в 3-х состояниях: Empty/Busy/Ready. Новое состояние 'Busy', как и для объекта класса CDbEvent, характеризует объект, находящийся в процессе ожидания ответа на запрос к СУБД ЛИНТЕР.

Добавлен новый метод-сигнал onState - вызывается при изменении состояния объекта. Метод retrieve изменен: открывается новый канал для работы (как в CDbEvent) и

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

Практическое занятие 12

Пример разработки приложения для ЛИНТЕР с использованием Qt 95

запрос посылается асинхронно с переводом в состояние 'Busy'. При получении ответа вызывается синхронный обработчик - статическая функция класса CSqlDS::onSelect - который переводит объект в состояние 'Ready' (вызов метода-сигнала onState) или вызывается метод-сигнал onError. При изменении состояния объекта CSqlDS в интерфейсном классе CDataSource вызывается метод-сигнал onFullChange и переустанавливается положение курсора.

Указания к практической работе:

1.Просмотрите исходные тексты 3-го этапа (директория step3). Имеются изменения в директории ctrl. Вместо списка в форму добавлен табличный элемент – класс CMyTable, который реализует работу с источником данных простого табличного визуального элемента, позаимствованного из стандартных примеров Qt (исходные тексты этого элемента находятся в поддиректории table; они взяты из примеров Qt как есть). Есть изменения также и в директории datasource: они касаются, в основном, асинхронной обработки. Соответствующим образом модифицирован и файл main.cpp.

26.Скомпилируйте исполняемый файл (команда make).

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

12.4. Задание для самостоятельной работы

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

Для этого в форме уже предусмотрены кнопки "Обновить", "Вставить", "Удалить". Необходимо добавить обработчики нажатия на эти кнопки, которые будут формировать и исполнять запросы UPDATE, UNSERT, DELETE для таблицы AUTO. При этом данные для этих запросов можно взять из четырех однострочных полей ввода, имеющихся в форме. Непосредственные вызовы Call-интерфейса ЛИНТЕР можно скрыть в классе CSqlDS, например, добавить в него метод modify, который будет в качестве параметра принимать текст запроса, и выполнять этот запрос при помощи команды Call-интерфейса “ “ (четыре пробела). Тогда задача обработчиков кнопок будет состоять в том, чтобы сформировать требуемый текст запроса (например, функцией sprintf, используя тексты однострочных полей) и передать его методу modify.

Отметим, что после модификации таблицы старая выборка, сделанная запросом SELECT, перестает отображать реальные данные. Простейший способ избежать этого – выбрать данные заново после их модификации (это позволяет, в том числе, использовать для INSERT,DELETE,UPDATE тот же канал ЛИНТЕР, т.к. результаты старой выборки SELECT нам все равно уже больше не понадобятся). Заметим, что в нашем примере данные автоматически будут перечитываться после любой операции модификации благодаря отслеживанию события БД.

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

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

Практическое занятие 12

96 Пример разработки приложения для ЛИНТЕР с использованием Qt

12.5. Пути развития класса – источника данных

Что еще не хватает в разработанном классе CDataSource?

Очевидно, буферизации данных. Буферизация необходима для того, чтобы исключить лишние обращения к базе данных и производить модификацию данных, не перечитывая данные из базы, а также поднять производительность обмена между ядром СУБД ЛИНТЕР и клиентом за счет использования команд пакетной загрузки и выгрузки данных GETM, PUTM.

Не хватает также управлением транзакциями RollBack/Commit.

Кроме того, источники данных можно делать связанными в соотношении 'Главныйподчиненный', организовать фильтрацию/сортировку в памяти, работу с выборкой с полной буферизацией на клиенте, быстрый поиск записи в выборке и многое другое.

В AsyncManager можно произвести оптимизацию по вызовам new/delete, если ввести список свободных объектов и использовать его как буфер свободных объектов.

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

97

Практическое занятие 13 Использование прекомпилятора встроенного SQL

Практика 6 часов (Лекция 11)

На занятии будут приведены примеры, иллюстрирующие работу прикладных программ с СУБД ЛИНТЕР, написанных на языке C с использованием встроенного SQL.

Будут рассмотрены следующие вопросы:

 

выполнение запросов без параметров;

 

обработка ошибок;

 

выполнение запросов с параметрами;

 

получение данных из выборки;

 

обработка результатов запроса с неизвестным форматом ответа;

 

выполнение хранимых процедур.

13.1. Компиляция примеров

Компиляция программ, написанных с использованием встроенного SQL, осуществляется в два этапа. Препроцессор pcc поставляется в дистрибутиве СУБД ЛИНТЕР. Результатом его работы является c-файл, который следует компилировать обычным компилятором:

prac13_e1: prac13_e1.pc

$(LINTER)/bin/pcc prac13_e1.pc prac13_e1.c $(CC) -c $(CFLAGS) prac13_e1.c

$(LINKER) prac13_e1.o $(LDKEY)prac13_e1 $(INTLIB) rm -f $@.o

С примерами поставляется makefile.

13.2. Соединение, выполнение запросов без параметров, обработка ошибок

Пример prac13_e1.pc иллюстрирует работу с запросами без параметров. Его

алгоритм таков:

 

 

создается соединение;

 

делается попытка удаления таблицы PRAC13_T1;

 

создается таблица PRAC13_T1;

 

вставляются данные;

 

делается выборка количества записей в переменную;

 

делается выборка поля NAME в переменную buf.

Следует обратить внимание на то, что вставка данных осуществляется в цикле из массива:

EXEC SQL WHENEVER SQLERROR GOTO not_inserted;

EXEC SQL FOR :i EXECUTE IMMEDIATE insert into PRAC13_T1 (id, name) values (:arr_i, 'aaa');

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

Практическое занятие 13

для этого переменной i присваивается количество повторений, в данном случае, 10.

98 Использование прекомпилятора встроенного SQL

Обработка ошибок осуществляется оператором WHENEVER. Для продолжения работы используется режим CONTINUE, для перехода на метку – GOTO, для вызова функции – CALL и для завершения приложения – STOP.

EXEC SQL WHENEVER SQLERROR CONTINUE;

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

EXEC SQL WHENEVER SQLERROR GOTO not_selected;

EXEC SQL SELECT count(*) from PRAC13_T1 into :i;

При этом переменная I должна быть описана в разделе DECLARE SECTION:

EXEC SQL BEGIN DECLARE SECTION;

short i = 10;

EXEC SQL END DECLARE SECTION;

Разрешено включать в запрос переменные:

EXEC SQL EXECUTE IMMEDIATE SELECT NAME into :buf from PRAC13_T1 where ID = :tid;

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

Задача 2. Модифицировать данный пример, добавив возможность обновления поля NAME пользователем в строке по указанному ID.

Задача 3. Модифицировать данный пример, добавив полноценный обработчик ошибок, анализирующий переменную ErrPCI_.

13.3. Запросы с параметрами

Для выполнения запроса с параметрами необходимо использовать операторы. В примере prac13_e2.pc для вставки строк в таблицу и для построения выборки используются запросы с параметрами:

 

создается соединение;

 

делается попытка удаления таблицы PRAC13_T1;

 

создается таблица PRAC13_T1;

 

создаются два оператора – один для вставки данных, другой – для

 

выборки;

 

в цикле: пользователь вводит данные одной строки, и выполняется

 

оператор вставки, причем данные передаются как параметры;

 

выбирается количество записей из таблицы;

 

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

 

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

 

номер принадлежит интервалу.

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

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

Практическое занятие 13

 

Использование прекомпилятора встроенного SQL

99

EXEC SQL PREPARE ST_INS FROM INSERT INTO PRAC13_T1 (id, name) VALUES (:tid, :tname);

EXEC SQL PREPARE ST_SEL FROM SELECT id, name into :tid, :tname from (SELECT id, name, rownum as rn FROM PRAC13_T1) WHERE Rn = :i;

Вставка данных:

scanf("%d %s", &tid, tname);

EXEC SQL EXECUTE ST_INS USING :tid, :tname;

Выборка данных:

for (i = n1; i <= n2; i++)

{

EXEC SQL EXECUTE ST_SEL USING :i into :tid, :tname;

printf("\t%d.\t%d\t%s\n", i, tid, tname);

}

13.4. Получение структуры ответа

Получение структуры ответа во встроенном SQL осуществляется при помощи дескрипторов. Работа с дескрипторами показана в файле примера prac13_e3.pc.

Алгоритм работы примера таков:

 

создается соединение;

 

резервируется память под дескриптор;

EXEC SQL ALLOCATE DESCRIPTOR :selectOutDesc;

 

пользователь вводит SELECT-запрос;

 

по запросу создается оператор;

EXEC SQL PREPARE ST_SEL FROM :szSQL;

делается описание выходных данных оператора в дескриптор;

EXEC SQL DESCRIBE OUTPUT ST_SEL INTO SQL DESCRIPTOR :selectOutDesc;

 

выводится тип и длина полей ответа;

 

привязывается к дескриптору буфер, в который следует помещать ответ;

for (i = 1; i <= colCount; i++)

{

p = &(buf[shift]);

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

10

Практическое занятие 13

 

0

Использование прекомпилятора встроенного SQL

 

 

 

EXEC SQL GET DESCRIPTOR :selectOutDesc VALUE :i :length =

LENGTH;

EXEC SQL SET DESCRIPTOR :selectOutDesc VALUE :i DATA = :p;

shift += align4(length);

}

описывается и открывается статический курсор;

EXEC SQL DECLARE CUR_SEL CURSOR FOR ST_SEL;

EXEC SQL OPEN CUR_SEL;

 

осуществляется проход по выборке.

for (i = 1; ;i++)

{

memset(buf, 0, sizeof(buf));

EXEC SQL FETCH CUR_SEL ABSOLUTE :i USING DESCRIPTOR :selectOutDesc;

if (ErrPCI_)

{

if (ErrPCI_ == 3000) printf("No more records\n"); else printf("Error code: %d\n", ErrPCI_);

break;

}

printAnswer();

}

В примере присутствует функция для определения имени типа по его номеру, возвращенного дескриптором (нумерация описана в разделе пользовательской документации «Встроенный SQL»). Для совместимости со Sparc-платформами предназначена функция align4, вычисляющая адрес, нацело делящийся на 4.

E-mail: market@relex.ru

ЗАО НПП «РЕЛЭКС»

http://www.relex.ru

 

 

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]