Russian Qt Forum

Qt => Базы данных => Тема начата: ритт от Ноябрь 04, 2007, 00:27



Название: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Ноябрь 04, 2007, 00:27
о подписке на сигналы о событиях от субд в кутэ4.4 уже было сказано в ветке http://prog.org.ru/forum/index.php/topic,6124.0.html (http://prog.org.ru/forum/index.php/topic,6124.0.html). в блогах появилась заметка http://labs.trolltech.com/blogs/2007/11/02/asynchronous-database-event-notifications/ (http://labs.trolltech.com/blogs/2007/11/02/asynchronous-database-event-notifications/), где говорится, что в снапшотах уже можно подписываться на такие нотификэйшены, используя постгре и птица

буду должен 5рэ или пиво (на выбор), если кто-нть найдёт время и хакнет эту фичу из снапшота под 4.3, т.к. у меня сейчас абсолютно нет свободного времени этим заниматься, а фича весьма полезная

плюс на будущее: как сделать такой нотификашен на мускуле? обязательно создавать ХП или можно как-то иначе/проще?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Dodge от Ноябрь 04, 2007, 00:58
я конечно не датабайс-гуру, но суть вопроса не совсем понял... что конкретно нада?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Ноябрь 04, 2007, 09:43
Здесь как-то пробегал код по этому поводу для FB поищи. :-)

Да, на FB для запуска события тебе всяко или триггер или процедура нужна. Сам сервер ничего не шлёт. :-)
Другое дело, что там 1 строчка. А вот если сервер этого не поддерживает, то всю коммуникацию по сети придётся устраивать самому.
Поэтому на том же мускуле проще опрашивать по таймеру...


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Ноябрь 04, 2007, 11:54
если ты про "опрашивать по таймеру", то я имею представление о какой ветке идёт речь (поискать).
непреемлемо, т.к. база предполагает быть весьма объёмной - вариант с таймером буду использовать в самом крайнем случае

ну, допустим, сделал я процедуру, выполняющуюся при добавлении строки в таблицу, что и куда процедура возвращать должна, чтобы клиент на другом конце получил сигнал?
раньше не приходилось с этим сталкиваться :(


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: SerjVarshavskiy от Ноябрь 04, 2007, 14:00
а зачем тебе опрашивать большую табл по таймеру?

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

вот как раз эту мини табл и опрашивай

в итоге минимум напряг на базу и сервер


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Ноябрь 04, 2007, 14:29
итого: что в маленькой всяческистранной таблице?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: SerjVarshavskiy от Ноябрь 04, 2007, 14:34
ну типа каждое поле это флаг
каждый флаг(поле) выставл в 1(труе) из тригеров в базе при каком-либо событии и тп
или в др значение

опрашиваешь эту табл по таймеру, если флаг поднят или больше-меньше какого-либо значения - делаешь
свои дела


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: baka от Ноябрь 04, 2007, 15:20
недели три назад я с такой проблемой столкнулся.


сделал по простому

добавил в БД таблицу в которой регистрируется каждое клиентское приложение
при подключении к БД. ( оно там оставляет свой ip, порт и т.д.).

при изменении к.либо данных каждый клиент оповещает других через TCP - сокеты
(о существовании других он узнает из БД).

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

реализовал пока топорно, еще буду переделывать, вот исходники :

.h
Код:
class Master : public QObject
{
// Обьект передающий уведомление о том что к.либо таблица изменилась

Q_OBJECT

public :
static Master * getObject();

public slots :
void tableChange(const QString & );
// этот слот связан со всеми  виджетами с помощью которых пользователь может менять данные
// он будет вызыватся каждый раз когда к.либо обьект проги изменил данные.

void beforeQuit();
void acceptConnection();

signals :
void RefreshTable(const QString & );
// сигнал рассылается всем виджетам
// в качестве параметра передается имя таблицы которая была изменена.

private :
Master(QObject * parent = 0);
static Master * pointer;
sendThread * sThread;
QTcpServer * tcpServer;
quint16 port;
QHostAddress hAdr;

};
class sendThread : public QThread
{
// Поток в котором будем рассылать уведомления другим клиентским прогам.

Q_OBJECT

public :
sendThread(QObject * parent = 0);
void start(const QString &);
void setAddress(QHostAddress & ,quint16 port);

protected:
void run();

private :
int q;
QString tName;
QSqlDatabase db;
QHostAddress hAdr;
quint16 port;
};


.cpp
Код:

Master * Master::pointer = 0;

Master::Master(QObject * parent)
:QObject(parent)
{
QSqlDatabase db = G::getDB();

hAdr = QHostInfo::fromName(QHostInfo::localHostName()).addresses().last();
// создаем сервер
tcpServer = new QTcpServer(this);
tcpServer->listen(hAdr);

port = tcpServer->serverPort();

// ДОБАВЛЯЕМ СЕБЯ В ip_table .
QSqlQuery query(G::getDB());
while(true)
{
if(!query.exec("SELECT max(`key`) FROM ip_table;"))
{
QMessageBox::information(0,"111",query.lastError().text(),QMessageBox::Yes);
};
query.next();
int key = query.value(0).toInt();
key++;

QString insert = "INSERT INTO ip_table VALUES("+QString::number(key)
+",'"+hAdr.toString()+"',"+QString::number(port)+",0);";
if(query.exec(insert)) break;
};

// Создаем sendThread
sThread = new sendThread(this);
sThread->setAddress(hAdr,port);

QObject::connect(tcpServer,SIGNAL(newConnection()),this, SLOT(acceptConnection()));
QObject::connect(QApplication::instance(),SIGNAL(aboutToQuit()),this,SLOT(beforeQuit()));
};

void Master::beforeQuit()// Слот связан с QCoreApplication::aboutToQuit ()   [signal]
{// Удаляем из ip_table данные о себе.
QString q("DELETE FROM ip_table WHERE ip='"+hAdr.toString()+"' AND port="+QString::number(port)+";");
QSqlQuery query(G::getDB());
if(!query.exec(q))
{
QMessageBox::information(0,QString("Удаление из ip_table не произошло.")
,query.lastError().text()+"\n"+q,QMessageBox::Yes);
};
// Если данные о себе не удалим перед выходом то ничего страшного они будут удалены другой клиентской программой. ( потом).
};


void Master::acceptConnection()
{
QTcpSocket * incomeConnection = tcpServer->nextPendingConnection();

QString str;
QDataStream in(incomeConnection);
in.setVersion(QDataStream::Qt_4_0);

while (incomeConnection->bytesAvailable() < (int)sizeof(quint16))
{
if (!incomeConnection->waitForReadyRead(6660))
{
return;
            };
};

quint16 size;
in>>size;
while(incomeConnection->bytesAvailable()<size)
{
if(!incomeConnection->waitForReadyRead(6660))
{
return;
};
};
in>>str;
emit RefreshTable(str);
};

void Master::tableChange(const QString & tName)
{
emit RefreshTable(tName); // все виджеты в данном экземпляре проги обновятся.
// и еще рассылаем всем другим прогам сообщение.
sThread->start(tName);
//G::debug("tableChange -"+tName);
};

Master * Master::getObject()
{
if(!pointer)
{
pointer = new Master(QApplication::instance());
};
return pointer;
};

sendThread::sendThread(QObject * parent)
:QThread(parent)
,port(0)
{
db=G::getDB();
};
void sendThread::setAddress(QHostAddress & adr,quint16 q)
{
hAdr = adr;
port = q;
};

void sendThread::start(const QString & tn)
{
if(hAdr.isNull())
{
QMessageBox::information(0,"Alarm",QString::fromLocal8Bit("Не инициализировали"),QMessageBox::Ok); 
return;
};
if(!port)
{
return ;
};

tName=tn;
QThread::start();
};

void sendThread::run()
{

QString q("SELECT `key`,`ip`,`port`,`error_count` FROM ip_table WHERE NOT (ip='"+hAdr.toString()+"' AND port="+QString::number(port)+");");
QSqlQuery query(db);
if(!query.exec(q))// выбираем из БД адреса и порты других приложений.
{
QMessageBox::information(0,"QueryError",query.lastError().text(),QMessageBox::Yes);
return;
};

QTcpSocket * tcpSocket = new QTcpSocket(this);
while(query.next())
{
int temp_key = query.value(0).toInt();
QString temp_ip = query.value(1).toString();
int temp_port = query.value(2).toInt();
tcpSocket->connectToHost(QHostAddress(temp_ip),temp_port,QIODevice::WriteOnly);


if(!tcpSocket->waitForConnected(666))
{
QSqlQuery del_q(db); // Если не дозвонились до к.либо клиента то увеличиваем счетчик ошибок иесли их больше трех удаляем из БД.
QString update("UPDATE ip_table SET error_count=error_count+1 WHERE ip= '"
+temp_ip+"' AND port="+QString::number(temp_port)+";");
if(!del_q.exec(update))
{
QMessageBox::information(0,"",del_q.lastError().text(),QMessageBox::Yes);
};
//QMessageBox::information(0,"QueryError","I am in (!tcpSocket->waitForConnected())",QMessageBox::Yes);
QString del("DELETE FROM ip_table WHERE error_count>3");
del_q.exec(del);
}
else
{
//отправляем имя таблицы.
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << (quint16)0;
out << tName;
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));

tcpSocket->write(block);
tcpSocket->disconnectFromHost();
};
tcpSocket->waitForDisconnected(666);
};
};


чтобы использовать такой механизм
каждый виджет работающий с БД должен содержать
сигнал для оповещения Master'а о том что таблица с таким-то именем была изменена
и слот принимающий имя таблицы которую надо переSELECT'ить
Код:
		
public slots:
void slt_refresh(const QString&);

signals :
void iChangeTable(const QString & );
Код:
		

void eMAbstractGrid::slt_refresh(const QString& tName)
{
if(tName!=this->tName) return;
setQuery(strQuery);
};
   
         


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Вячеслав от Ноябрь 04, 2007, 15:26
Мдя ;) Перефразируя "чего только народ н придумает ,шоб трехзвенку не юзать ";) А почему-бы не замутить app-server и не цепляться к нему ? ;) Хотя все зависит от масшабов задачи - если клиентов пара-тройка - то и тригеры\event'ы сойдут, а если пара десятков да еще с hot-репликацией бд - то ой ....


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Ноябрь 04, 2007, 15:27
спасибо. вполне развёрнуто.
только в моём случае, к сожалению, данный вариант неприменим по ряду причин :(


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Ноябрь 04, 2007, 18:54
Цитировать
Вот  мой унитаз, вот так я на нём сижу, дайте мне таки мою туалетную бумагу! (c) рус.нар.анекдот.
Решение для FB:
Для таблицы, для которой нужно получать извещение о том, что данные в ней добавились/изменились/удалились, создаём триггер:
Код:
CREATE OR ALTER trigger PHYS_PERSONS_MODIFY for PHYS_PERSONS
active after insert or update or delete position 0
AS
begin
  post_event 'PHYS_PERSONS_MODIFY';
end
На клиенте ждём в потоке прихода события.
По получению выясняем что произошло, и корректируем представления.
Для трёхзвенки так же.
Всё.


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Ноябрь 04, 2007, 19:06
вооот. популярно и доходчиво!
тонал, +1

а для мускуля, говорите, никак? надо бы костыль придумать...
для трёзвенки реализация очевидна...с клиент-сервер сложнее
пока что из мыслей только отправка предопределённого мессага по тцп/ип - надо доки курить
у кого ещё какие соображения?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: WW от Ноябрь 06, 2007, 00:33
Тут как-то приводились модифицированные исходники qt-драйвера для PostreSQL. Поищи. С утра гляну на работе. Реализуют нотификацию.


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: lit-uriy от Май 08, 2008, 23:06
Решение для FB:
...
На клиенте ждём в потоке прихода события.
Tonal, объясни с этого места пожалуйста, можешь пример кода привести?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Май 10, 2008, 13:27
А что объяснять то? Поток, ожидание, корректировку?
Поток - QThread
Ожидание - смотри доки FB/IB по работе с api событий.
Корректировка моделей - зависит от твоих моделей. :)

Да, вроде Троли обещали события базы поддержать для PG/FB/IB - смотри, может в 4.4 уже всё есть. :)


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: lit-uriy от Май 10, 2008, 21:17
Поток - QThread
Ожидание - смотри доки FB/IB по работе с api событий.
вот это, как в Qt'ях неQt'явое событие ловить?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Май 11, 2008, 12:26
У меня в python-е схемотично так:
Код:
      self.__conduit = conn.event_conduit(self.__events)
      evs = self.__conduit.wait(time)
      if evs:
        self.__send_msg(evs)
Как будет на С++ - надо смотреть либо client api, либо библиотеку-обёртку, которую будешь использовать.


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: White Owl от Май 12, 2008, 18:05
Да, вроде Троли обещали события базы поддержать для PG/FB/IB - смотри, может в 4.4 уже всё есть. :)
А можно ссылку на это обещание? Или это было где-то в ньюсах? Про ODBC они ничего подобного не обещали?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: ритт от Май 12, 2008, 19:05
Да, вроде Троли обещали события базы поддержать для PG/FB/IB - смотри, может в 4.4 уже всё есть. :)
А можно ссылку на это обещание? Или это было где-то в ньюсах? Про ODBC они ничего подобного не обещали?

ссылки в начале треда


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Май 12, 2008, 20:08
А разве ODBC умеет рассылать события из баз?


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: White Owl от Май 12, 2008, 20:51
А разве ODBC умеет рассылать события из баз?
Сам по себе нет. Но например для драйвера ASA можно сделать так:
Код:
// колбек-обработчик сообщений
void SQL_CALLBACK my_msgproc(
   VOID * sqlca,
   UNSIGNED CHAR msg_type,
   LONG code,
   UNSIGNED SHORT len,
   CHAR* msg )
{ ... }

// и после установления коннекта:
rc = SQLSetConnectAttr(
   dbc,
   SA_REGISTER_MESSAGE_CALLBACK,
   (SQLPOINTER) &my_msgproc, SQL_IS_POINTER );
Естественно константа SA_REGISTER_MESSAGE_CALLBACK определена в собственном заголовке.


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: Tonal от Май 13, 2008, 07:07
И как это должно выглядеть в QODBC?
Подключать всегда заголовки ASA? :)

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


Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: White Owl от Май 13, 2008, 17:58
Ну да, я так и делаю пока: получаю хендл коннекта через db.driver()->handle(), и самостоятельно зову SQLSetConnectAttr(). Для этого конечно еще и дополнительно линкуюсь с odbc32.dll. Работает.
Но хотелось бы более гладкого кода а раз уж в 4.4 обещан QSqlDriver::subscribeToNotification()... то... Хотя да, для универсального драйвера интерфейса типа ODBC это действительно вряд-ли получится сделать красиво. Но в принципе, можно все-же слегка облегчить жизнь:
Добавить в пару к QSqlDatabase::setConnectOptions еще один метод типа:
db.setConnectOption(int option, void*pointer);
А в QSqlDriver вынести опции коннекта из open() в отдельный метод и ... все. Этого хватит для использования уникальных для БД опций.



Название: Re: 4.3.2: подписка на сигналы о событиях от субд?
Отправлено: DpoHro от Июнь 12, 2008, 00:17
Прокурил всю тему.
Значит пока что для mySQL нет решения?