Подписка из Qt Script на уведомления PostgreSQL

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск

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

При программировании в среде pl/pgSQL для вывода сообщений можно использовать команду RAISE имеющую синтаксис:


RAISE [ level ] 'format' [, expression [, ...]] [ USING OPTION = expression [, ... ] ];
RAISE [ level ] condition_name [ USING OPTION = expression [, ... ] ];
RAISE [ level ] SQLSTATE 'sqlstate' [ USING OPTION = expression [, ... ] ];
RAISE [ level ] USING OPTION = expression [, ... ];
RAISE ;

Например:

RAISE NOTICE 'This is notice with expression % + % arg %' , 1, 1, 1 + 1

Подробнее об этой команде можно прочесть здесь

По умолчанию клиентская библиотека libpq, используемая для подключения к pgsql серверу, выводит сгенерированные на стороне сервера сообщения в stderr (*nix) или отладочную консоль (win*). Однако данными сообщениями можно оповещать не только программиста о происходящем в недрах sql сервера, но и пользователя. Например, в триггере, который отклонил вставку записи, генерировать сообщение, содержащее причину таких действий. В клиентском приложении в свою очередь выводить сообщение в окно. Предполагается что, приложение использует Qt Script Framework, однако данный после небольшого сокращения можно будет использовать просто в Qt c++.

Для начала следует определить несколько типов, а точнее просто скопировать их из lipq-fe.h:

// Подключаем qt sql psql драйвер
#include <QtSql/QSqlDatabase>
#include <QtSql/qsql_psql.h>
 
// Переменные обозначающие части сообщений сервера
#define PG_DIAG_SEVERITY		'S'
#define PG_DIAG_SQLSTATE		'C'
#define PG_DIAG_MESSAGE_PRIMARY 'M'
#define PG_DIAG_MESSAGE_DETAIL	'D'
#define PG_DIAG_MESSAGE_HINT	'H'
#define PG_DIAG_STATEMENT_POSITION 'P'
#define PG_DIAG_INTERNAL_POSITION 'p'
#define PG_DIAG_INTERNAL_QUERY	'q'
#define PG_DIAG_CONTEXT			'W'
#define PG_DIAG_SOURCE_FILE		'F'
#define PG_DIAG_SOURCE_LINE		'L'
#define PG_DIAG_SOURCE_FUNCTION 'R'
 
// Функция-подписчик на PostgreSQL нотисы
typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);
 
// Функция устанавливающая подписчика на PostgreSQL нотисы
typedef PQnoticeReceiver (*PQsetNoticeReceiver)(PGconn *conn,
					PQnoticeReceiver proc,
					void *arg);
 
// Функция возвращающая определенные части сообщения
typedef char * (*PQresultErrorField)(const PGresult *res, int fieldcode);
 
// Переменные хранящие указатели на функции
PQsetNoticeReceiver pqSetNoticeReceiver;
PQresultErrorField pqResultErrorField;

Далее загружаем библиотеку:

#include <QtCore/QLibrary>
 
/*!
	Загрузка libpq
	\return bool
	\retval true успешно
	\retval false не успешно
*/
bool initPostgresqlLibrary()
{
	QLibrary library("libpq");
	if (library.load()) {
		pqSetNoticeReceiver = (PQsetNoticeReceiver)(library.resolve("PQsetNoticeReceiver"));
		pqResultErrorField = (PQresultErrorField)(library.resolve("PQresultErrorField"));
		return pqSetNoticeReceiver && pqResultErrorField;
	}
	return false;
}

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

#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
// Переменная хранящая функцию в qt script, обрабатывающая нотисы
QScriptValue scriptNoticeReceiver = QScriptValue();
 
// Вызывает qt script функцию с 4-мя параметрами severity, primary, detail, hint
// severity - уровень сообщения
void PostgreSQLnoticeReceiver(void *arg ,const PGresult *res)
{
	QString severity = QString::fromUtf8(pqResultErrorField(res, PG_DIAG_SEVERITY));
	QString primary = QString::fromUtf8(pqResultErrorField(res, PG_DIAG_MESSAGE_PRIMARY));
	QString detail = QString::fromUtf8(pqResultErrorField(res, PG_DIAG_MESSAGE_DETAIL));
	QString hint = QString::fromUtf8(pqResultErrorField(res, PG_DIAG_MESSAGE_HINT));
 
	//qDebug() << severity << primary << detail << hint;
	if (scriptNoticeReceiver.isFunction()) {
		QScriptValueList arguments;
		arguments << severity << primary << detail << hint;
		scriptNoticeReceiver.call(scriptNoticeReceiver.engine()->globalObject(), arguments);
	}
}

Определяем функцию оболочку для qt script, выполняющая подписку на pgsql нотисы. Данная функция будучи в qt script принимает два параметра:

  • Имя qt sql соединения
  • Объект-функцию

Возвращает true в случае успеха.

QScriptValue PQsetNoticeReceiverWrapper(QScriptContext* context, QScriptEngine* /*engine*/)
{
	if (context->argumentCount() == 2) {
		QString connectionName = context->argument(0).toString();
		if (QSqlDatabase::contains(connectionName) && context->argument(1).isFunction()) {
			QVariant driverHandle = QSqlDatabase::database(connectionName).driver()->handle();
			if (!QString::compare(driverHandle.typeName(),"PGconn*")) {
				PGconn *handle = *static_cast<PGconn **>(driverHandle.data());
				if (handle != 0) {
					scriptNoticeReceiver = context->argument(1);
					if (initPostgresqlLibrary()) {
						pqSetNoticeReceiver(handle, PostgreSQLnoticeReceiver, 0);
						return true;
					}
				}
			}
		}
	}
	return false;
}

Регистрируем функцию в qt script:

QScriptEngine *engine = new QScriptEngine();
.......
engine->globalObject().setProperty("PQsetNoticeReceiver", engine->newFunction(PQsetNoticeReceiverWrapper));

Теперь у глобального объекта qt script есть свойство-функция PQsetNoticeRecevier(connection, function).

Определяем в qt script функцию-подписчика:

noticeReceiver = function(severity, primary, detail, hint) {
	message = primary;
	if (detail != "")
		message += "\n" + detail;
	if (hint != "")
		message += "\n" + detail;
	if (severity == "WARNING") {
		print(message);
	} else if (severity == "NOTICE") {
		print(message);
	} else if (severity == "INFO") {
		print(message);
	} else if (severity == "LOG") {
		print(message);
	}
};

Выполняем подписку в qt script:

if (!PQsetNoticeReceiver(sqlConnectionName, noticeReceiver))
		print("Unable to set postgresql notice receiver");