Russian Qt Forum

Qt => Базы данных => Тема начата: Odyssey от Февраль 16, 2011, 13:57



Название: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 16, 2011, 13:57
Здравствуйте!
Я новичок, потому заранее прошу извинить, если вопрос не слишком умный  :)

Есть БД на сервере и два приложения на двух разных компьютерах, совместно использующие эту БД.

Как приложение на одном компьютере может понять, что другое приложение изменило эту БД (поменяло значение поля записи таблицы и т.д.)?

Конечно, можно создать сетевые компоненты и при любом изменении посылать через них сообщение "соседу", но, может, есть какой-нибудь более изящный способ? Скажем, какой-нибудь сигнал от самой БД и т.п. 


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 16, 2011, 14:17
Смотря что за БД. В некоторых есть notify, на которые можно подписаться.


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 16, 2011, 15:10
Спасибо, Пантер :)

У меня PostgreSQL версии 7.4.2. Судя по мануалу, команды LISTEN и NOTIFY он понимает.
Однако как мне "поднять" сигнал, пришедший на уровне базы данных, на уровень Qt-приложения?
Как я понимаю, для этого необходимо предварительно подключить и использовать специальные библиотеки libpq или libpgtcl. А можно ли обойтись стандартными библиотеками Qt?

(Кстати, а если приложения на разных компах однотипные? Получается, они всё равно должны каждое выполнять команду NOTIFY name с различным аргументом name? А то ведь само БД изменит и себе же (в том числе) сигнал отправит - и как отличить, само себе приложение сигнал послало и ничего не делать, или всё же приложение на другом компе и нужно новую информацию как-то обработать?)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 16, 2011, 16:32
На уровне приложения читай bool QSqlDriver::subscribeToNotification ( const QString & name ).
На уровне базы данных, сделай триггера на обновление/добавление/удаление и в них делай NOTIFY.
Сам использовал с PostgreSQL, работает отлично.


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 17, 2011, 12:56
Спасибо, сенсей!

Но еще несколько вопросов появилось ))

I. Читал оригинальный мануал для версии 7.4.2 с "родного" сайта. Там в 35-й главе описываются триггерные функции (trigger functions). Это оно? Если да, то там указано, что функции могут быть написаны только на языке С, а на языке SQL они пока быть не могут. Как же тогда выполнить команду NOTIFY? (Есть там также пример, но в нем опять подключение дополнительных библиотек. Это ведь не то, о чём ты говорил?).

II. Попытался сделать обмен сигналами без отслеживания изменений таблиц на уровне баз данных. Что-то получилось, но не всё так, как я ожидал. Привожу простейший пример, где в конструкторе использована функция subscribeToNotification и привязка слота формы к сигналу драйвера Notification, а по нажатию кнопки исполняется запрос "NOTIFY n1" .

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

Код:
main.cpp

#include <QApplication>
#include "qmywidget.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QMyWidget wgt(0);
    return app.exec();
}

qmywidget.h

#ifndef QMYWIDGET_H
#define QMYWIDGET_H

#include <QWidget>

class QPlainTextEdit;
class QPushButton;
class QVBoxLayout;

class QMyWidget : public QWidget
{
    Q_OBJECT

protected:
    virtual void closeEvent(QCloseEvent*);

public:
    QMyWidget(QWidget * = 0);
    QPlainTextEdit *pTE;
    QPushButton *cmdNotify;
    QVBoxLayout *layout;

public slots:
    void slotNotify();
    void slotNotification(const QString &);

};

#endif // QMYWIDGET_H


qmywidget.cpp

#include <QPlainTextEdit>
#include <QPushButton>
#include <QtSql>
#include <QVBoxLayout>
#include "qmywidget.h"

QMyWidget::QMyWidget(QWidget *parent) : QWidget(parent)
{
    pTE = new QPlainTextEdit(this);
    cmdNotify = new QPushButton("Notify", this);
    layout = new QVBoxLayout();
    layout->addWidget(pTE);
    layout->addWidget(cmdNotify);
    setLayout(layout);

    QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL7");
    db.setDatabaseName("test");
    db.setHostName("localhost");
    db.setPort(***);
    db.setUserName(***);
    db.setPassword(***);
    if (!db.open()) {
      qDebug() << "Database opened: " << db.lastError();
    }
    else { qDebug() << "Database error";}

    db.driver()->subscribeToNotification("n1");
    connect(db.driver(), SIGNAL(notification(const QString&)), this, SLOT(slotNotification(const QString&)) );
    connect(cmdNotify, SIGNAL(clicked()), this, SLOT(slotNotify()));
    show();
}

void QMyWidget::slotNotification(const QString &Notif) {
    pTE->appendPlainText(Notif);
}

void QMyWidget::slotNotify() {
    pTE->appendPlainText("Notify button pressed");
    QSqlQuery queryNotify("NOTIFY n1;");
}

void QMyWidget::closeEvent(QCloseEvent *event) {
    QSqlDatabase db = QSqlDatabase::database();
    if (db.isOpen()) { db.close(); }
}

Однако получается, что сигнал notify иногда проходит несколько раз при нажатии кнопки. Подозреваю, что я как-то некорректно обошелся с запросом, и каким-то образом он исполняется несколько раз. Но я, вроде бы, делаю экземпляр запроса в стеке и при выходе из функции он должен быть уничтожен?

III. К этой же небольшой программке.
Почему в строке "QMyWidget wgt(0);" мне непременно нужно указывать аргумент? Если не указать, то виджет невидим, но ведь я вроде бы и так задаю это нулевое значение по умолчанию?


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 17, 2011, 13:19
1. http://www.postgresql.org/docs/8.1/static/sql-createtrigger.html
2. Уведомление ловит как отправляющий экземпляр, так и второй.
3. Странно. Вот так вот не работает:
Код
C++ (Qt)
QMyWidget wgt;
?


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 17, 2011, 14:29
Пантер еще раз и бесконечно - большое спасибо! :)))  Разрешишь попытать тебя еще немножко? )

1. Итак, функцию, вызываемую при спуске триггера всё равно придётся писать на С? Это я понимаю из приведенного примера. Сам-то триггер создается командой SQL и функция объявляется тоже командой SQL:

CREATE FUNCTION trigf() RETURNS trigger
    AS 'filename'
    LANGUAGE C;

Но перед этим функция должна быть предварительно написана на С и скомпилирована в файл filename, верно? Да и возвращать она обязана некий тип trigger, определенный в одной из сишных подключаемых библиотек...

Если всё "так и задумано" - буду изучать :)

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

Пример:
В экземпляре А кнопка нажимается 5 раз. В экземпляре Б при каждом нажатии появляется ровно один сигнал (т.е. надпись "n1"). Вроде, так и должно быть. Потом я нажимаю кнопку экземпляра Б. В А появляется разом аж 6 сигналов. При повторном нажатии Б сигнал в А опять только 1. При нажатии А в Б приходит уже три сигнала и т.д.
В общем, ничего не приходит в голову, кроме как ошибок с констуированием и уничтожением экземпляров объекта запроса... (пытался его методом тыка и в динамической памяти делать - тот же эффект).

3. А вот так работает! ("Чёрт побери, Холмс... но КАК?")  ;)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 17, 2011, 14:41
Ух, заставил покопаться в давно забытых знаниях по sql. :)
Код:
CREATE TRIGGER name_of_trigger FOR some_table
AFTER UPDATE AS
BEGIN
  NOTIFY n1;
END


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 17, 2011, 17:09
Эх, не прокатило... выдавало ошибку уже на FOR.

Чуть поправил вот так:

Код:
CREATE TRIGER trigg AFTER UPDATE ON table1 AS
BEGIN
     NOTIFY n1;
END

...но все равно выдает ошибку "в районе AS".  :'(  
Должно быть, версия PSQL не та... (данную sql-команду пытался выполнить в программе pgAdmin III)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 17, 2011, 17:15
В pgAdmin на таблице в контекстном меню должно быть Создать триггер.


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: vlad-mal от Февраль 17, 2011, 20:27
Odyssey, в 99,99 % случаев это не нужно. Я синхронное обновление данных на клиентах.
Ну, представь себе, что ты узнал об изменениях - ну и что? Хочешь, чтобы данные у разных клиентов мелькали туда-сюда? А если на клиенте данных много? А если каскадные обновления?
Ну и как отображать новые данные? Предположим - это список. С какого место его отображать? А если это "место" удалено? ...(и т.д.)

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

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


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 21, 2011, 11:36
Пантер

Create trigger не наблюдается - есть только Create trigger function. В хелпе, привинченном к пгАдмину, повторяется всё та же вещь - писать триггерные функции на простом языке функций Sql ("plain SQl function language") невозможно. Когда я пытаюсь создать функцию через команду Create trigger function, выбираю язык internal и прописываю в окне Parameter строчку NOTIFY n1, мне выдают, что, к сожалению, нет у них такой встроенной функции NOTIFY :(

(В общем, проникся еще большим уважением к сенсеям, у которых такое получается ^___^ Я-то уже начинаю испытывать определенное уныние...)

vlad-mal

Спасибо за подсказку. )) Дело в том, что клиентов там мало, обновления на экране достаточно редкие. Один из них находится в ждущем режиме и должен реагировать на ввод даных другим. Потому кнопка "Обновить" не вполне подходит. Возможно, стоит сделать регулярные автоматические обновления по таймеру? Можно еще передать сообщение по сети, но хотелось использовать средства БД, если уж они есть, и доразобраться всё же с триггерами и уведомлениями. 


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 21, 2011, 12:10
Покопался в старом своем проекте. Вот как нужно:
Код
SQL
CREATE RULE notify_some_table_update_rule AS ON UPDATE TO some_table DO NOTIFY some_table_updated;
 
Удачи. ;)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: vlad-mal от Февраль 21, 2011, 13:44
Есть опыт использования подобных извещений, правда, с FireBird.
Все заинтересованные клиенты подписываются на определенное сообщение.
При модификации данных (триггер на удаление/изменение/добавление), в случае подтверждения транзакции, подписчики получают уведомление. Ну и могут, к примеру, перечитать данные.


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 21, 2011, 14:02
Спасибо, сенсей, сигнал при изменении таблицы посылается и ловится! ))

Только осталась всё та же непонятная фигня, что и с исполняемыми запросами "NOTIFY n1;" (см. выше).
Т.е. количество уведомляющих сигналов, приходящихся на одно изменение, "плавает" от 1 до сколько угодно (по тому же алгоритму). (Глюк базы, значит?)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Odyssey от Февраль 21, 2011, 14:05
Есть опыт использования подобных извещений, правда, с FireBird.
Все заинтересованные клиенты подписываются на определенное сообщение.
При модификации данных (триггер на удаление/изменение/добавление), в случае подтверждения транзакции, подписчики получают уведомление. Ну и могут, к примеру, перечитать данные.


Я нуб настолько, что для меня все техники внове и потому сейчас пробую то, до чего удается дотянуться. ))
И - да, спасибо, примерно так и намеревался использовать извещения. )


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Февраль 21, 2011, 14:09
У меня такого глюка не было. Посмотри, может ты где-то несколько раз подписываешься или изменяешь что-то.


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: vladimir_l от Март 25, 2011, 14:15
Однако в то приложение, где была нажата кнопка сигнал никогда не идет.

Столкнулся с такой же проблемой. может кто-нибудь знает как её побороть. Т.е. сигнал не приходит если база была изменена из этого же приложения. У меня изменения в базу вносятся в другом окне, не связанном с тем окном где отображается информация, не хочется реализовывать сигналы в пределах приложения. 


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: Пантер от Март 25, 2011, 14:19
Может, ты в транзакции делаешь и не коммитишь?


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: vladimir_l от Март 25, 2011, 15:12
Может, ты в транзакции делаешь и не коммитишь?
Всё разобрался. Дело не в транзакциях. Постгрес не посылает уведомления тому пиду котрорый внёс изменения. В документации по NOTIFY так и написано:
It is common for a client that executes NOTIFY to be listening on the same notification name itself. In that case it will get back a notification event, just like all the other listening sessions. Depending on the application logic, this could result in useless work, for example, reading a database table to find the same updates that that session just wrote out. It is possible to avoid such extra work by noticing whether the notifying session's server process PID (supplied in the notification event message) is the same as one's own session's PID (available from libpq). When they are the same, the notification event is one's own work bouncing back, and can be ignored. (Despite what was said in the preceding paragraph, this is a safe technique. PostgreSQL keeps self-notifications separate from notifications arriving from other sessions, so you cannot miss an outside notification by ignoring your own notifications.)


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: chu от Март 29, 2011, 14:45
Смотря что за БД. В некоторых есть notify, на которые можно подписаться.
как быть с mySQL? там нет notify


Название: Re: Совместное использование БД и уведомление о ее изменении
Отправлено: vlad-mal от Апрель 03, 2011, 17:46
...Постгрес не посылает уведомления тому пиду котрорый внёс изменения...
Полезная фича. Я когда-то на FireBird боролся с этим.
С другой стороны, когда не смог побороть, то просто стал ее использовать: данные (в пределах зоны отображения, конечно) обновлялись только по получении извещения, неважно, кто их модифицировал - я или кто-то еще.