Подписка из 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");