diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ simple_option(KDB_EXPRESSION_DEBUG "Debugging of Expression classes" OFF) simple_option(KDB_DRIVERMANAGER_DEBUG "Debugging of the Driver Manager class" OFF) simple_option(KDB_TRANSACTIONS_DEBUG "Debugging of the Transaction class" OFF) +simple_option(KDB_TABLESCHEMACHANGELISTENER_DEBUG "Debugging of the KDbTableSchemaChangeListener class" OFF) # Public options (affecting public behavior or contents of KDb) simple_option(KDB_DEBUG_GUI "GUI for debugging" OFF) diff --git a/src/KDbConnection.h b/src/KDbConnection.h --- a/src/KDbConnection.h +++ b/src/KDbConnection.h @@ -38,6 +38,7 @@ class KDbServerVersionInfo; class KDbSqlResult; class KDbTableSchemaChangeListener; +class KDbTableSchemaChangeListenerPrivate; class KDbTransactionGuard; class KDbVersionInfo; @@ -1408,7 +1409,7 @@ friend class KDbProperties; //!< for setError() friend class KDbQuerySchema; friend class KDbQuerySchemaPrivate; - friend class KDbTableSchemaChangeListener; + friend class KDbTableSchemaChangeListenerPrivate; friend class KDbTableSchema; //!< for removeMe() }; diff --git a/src/KDbConnection.cpp b/src/KDbConnection.cpp --- a/src/KDbConnection.cpp +++ b/src/KDbConnection.cpp @@ -274,6 +274,7 @@ void KDbConnectionPrivate::removeTable(const KDbTableSchema& tableSchema) { + KDbTableSchemaChangeListener::unregisterForChanges(conn, &tableSchema); m_tablesByName.remove(tableSchema.name()); KDbTableSchema *toDelete = m_tables.take(tableSchema.id()); delete toDelete; diff --git a/src/KDbConnection_p.h b/src/KDbConnection_p.h --- a/src/KDbConnection_p.h +++ b/src/KDbConnection_p.h @@ -148,6 +148,8 @@ QHash* > tableSchemaChangeListeners; + QHash* > queryTableSchemaChangeListeners; + //! Used in KDbConnection::setQuerySchemaObsolete( const QString& queryName ) //! to collect obsolete queries. THese are deleted on connection deleting. QSet obsoleteQueries; diff --git a/src/KDbTableSchemaChangeListener.h b/src/KDbTableSchemaChangeListener.h --- a/src/KDbTableSchemaChangeListener.h +++ b/src/KDbTableSchemaChangeListener.h @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2017 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -23,7 +23,9 @@ #include class KDbConnection; +class KDbQuerySchema; class KDbTableSchema; +class KDbTableSchemaChangeListenerPrivate; //! @short An interface allowing to listen for table schema changes /** @@ -67,50 +69,112 @@ */ void setName(const QString &name); - //! @todo will be more generic - /** Registers @a listener for receiving (listening) information about changes - in table schema @a table. Changes could be related to altering and removing. */ + /** Registers @a listener for receiving (listening) information about changes in table schema + * @a table and all tables related to lookup fields. Changes can be related to altering and + * removing. + */ static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener, const KDbTableSchema* table); + /** + * Registers @a listener for receiving (listening) information about changes in query schema + * @a query and all tables that the query uses. + * + * All tables related to lookup fields of these tables are also checked. + * Changes can be related to table altering and removing. + */ + static void registerForChanges(KDbConnection *conn, + KDbTableSchemaChangeListener* listener, + const KDbQuerySchema* query); + /** * Unregisters @a listener for receiving (listening) information about changes * in table schema @a table. */ static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener, const KDbTableSchema* table); + /** + * Unregisters all listeners for receiving (listening) information about changes + * in table schema @a table. + */ + static void unregisterForChanges(KDbConnection *conn, + const KDbTableSchema* table); + /** * Unregisters @a listener for receiving (listening) information about changes - * in any table schema. + * in any table or query schema. */ static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener); + /** + * Unregisters @a listener for receiving (listening) information about changes + * in query schema @a query. + */ + static void unregisterForChanges(KDbConnection *conn, + KDbTableSchemaChangeListener* listener, + const KDbQuerySchema* query); + + /** + * Unregisters all listeners for receiving (listening) information about changes + * in query schema @a query. + */ + static void unregisterForChanges(KDbConnection *conn, + const KDbQuerySchema* query); + + /** + * @return list of all table schema listeners registered for receiving (listening) + * information about changes in table schema @a table and other tables or queries depending + * on @a table. + */ + static QList listeners(KDbConnection *conn, + const KDbTableSchema *table); + /** * @return list of all table schema listeners registered for receiving (listening) - * information about changes in table schema @a table. + * information about changes in query @a query and other tables or queries depending on @a query. */ - static QList listeners( - const KDbConnection *conn, const KDbTableSchema* table); + static QList listeners(KDbConnection *conn, + const KDbQuerySchema *query); /** - * Closes all table schema listeners for table schema @a table. - * See KDbTableSchemaChangeListener::closeListener() for explanation - * of the operation of closing listener. + * Closes all table schema listeners for table schema @a table except for the ones from + * the @a except list. + * + * See KDbTableSchemaChangeListener::closeListener() for explanation of the operation + * of closing listener. + * + * @return true if all listenters for the table schema @a table have been successfully closed + * (returned true) or @c false or @c cancelled if at least one listener returned + * @c false or @c cancelled, respectively. + * Regardless of returned value, closeListener() is called on all listeners for @a table. + */ + static tristate closeListeners(KDbConnection *conn, const KDbTableSchema* table, + const QList &except + = QList()); + + /** + * Closes all table schema listeners for query schema @a query except for the ones from + * the @a except list. + * + * See KDbTableSchemaChangeListener::closeListener() for explanation of the operation + * of closing listener. + * * @return true if all listenters for the table schema @a table have been successfully closed * (returned true) or @c false or @c cancelled if at least one listener returned * @c false or @c cancelled, respectively. * Regardless of returned value, closeListener() is called on all listeners for @a table. */ - static tristate closeListeners(KDbConnection *conn, const KDbTableSchema* table); + static tristate closeListeners(KDbConnection *conn, const KDbQuerySchema* query, + const QList &except + = QList()); private: Q_DISABLE_COPY(KDbTableSchemaChangeListener) - class Private; - Private * const d; + KDbTableSchemaChangeListenerPrivate * const d; }; #endif diff --git a/src/KDbTableSchemaChangeListener.cpp b/src/KDbTableSchemaChangeListener.cpp --- a/src/KDbTableSchemaChangeListener.cpp +++ b/src/KDbTableSchemaChangeListener.cpp @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2017 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -20,17 +20,334 @@ #include "KDbTableSchemaChangeListener.h" #include "KDbConnection.h" #include "KDbConnection_p.h" +#include "KDbLookupFieldSchema.h" +#include "kdb_debug.h" -class Q_DECL_HIDDEN KDbTableSchemaChangeListener::Private +#ifdef KDB_TABLESCHEMACHANGELISTENER_DEBUG +# define localDebug(...) kdbDebug(__VA_ARGS__) +#else +# define localDebug(...) if (true) {} else kdbDebug(__VA_ARGS__) +#endif + +class KDbTableSchemaChangeListenerPrivate { public: - Private() {} + KDbTableSchemaChangeListenerPrivate() + { + } + + //! Registers listener @a listener for changes in table @a table + static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener, + const KDbTableSchema *table) + { + Q_ASSERT(conn); + Q_ASSERT(listener); + Q_ASSERT(table); + QSet* listeners = conn->d->tableSchemaChangeListeners.value(table); + if (!listeners) { + listeners = new QSet(); + conn->d->tableSchemaChangeListeners.insert(table, listeners); + } + localDebug() << "listener=" << listener->name() << "table=" << table->name(); + listeners->insert(listener); + } + + //! Registers listener @a listener for changes in query @a query + static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener, + const KDbQuerySchema *query) + { + Q_ASSERT(conn); + Q_ASSERT(listener); + Q_ASSERT(query); + QSet *listeners + = conn->d->queryTableSchemaChangeListeners.value(query); + if (!listeners) { + listeners = new QSet(); + conn->d->queryTableSchemaChangeListeners.insert(query, listeners); + } + localDebug() << "listener=" << listener->name() << "query=" << query->name(); + listeners->insert(listener); + } + + //! Unregisters listener @a listener for changes in table @a table + static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener, + const KDbTableSchema *table) + { + Q_ASSERT(conn); + Q_ASSERT(table); + QSet *listeners + = conn->d->tableSchemaChangeListeners.value(table); + if (!listeners) { + return; + } + localDebug() << "listener=" << (listener ? listener->name() : QString::fromLatin1("")) + << "table=" << table->name(); + if (listener) { + listeners->remove(listener); + } else { + listeners->clear(); + } + } + + //! Unregisters listener @a listener for changes in query @a query + static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener, + const KDbQuerySchema *query) + { + Q_ASSERT(conn); + Q_ASSERT(query); + QSet *listeners + = conn->d->queryTableSchemaChangeListeners.value(query); + if (!listeners) { + return; + } + localDebug() << "listener=" << (listener ? listener->name() : QString::fromLatin1("")) + << "query=" << query->name(); + if (listener) { + listeners->remove(listener); + } else { + listeners->clear(); + } + } + + //! Unregisters listener @a listener for any changes + static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener) + { + Q_ASSERT(conn); + Q_ASSERT(listener); + localDebug() << "listener=" << listener->name(); + for (QSet *listeners : conn->d->tableSchemaChangeListeners) { + listeners->remove(listener); + } + for (QSet *listeners : conn->d->queryTableSchemaChangeListeners) { + listeners->remove(listener); + } + } + + //! Returns @c true if @a table1 depends on @a table2, that is, if: + //! - @a table1 == @a table2, or + //! - @a table1 has lookup columns that reference @a table2 + static bool tableDependsOnTable(QSet *checkedTables, + QSet *checkedQueries, + KDbConnection *conn, const KDbTableSchema *table1, + const KDbTableSchema *table2) + { + if (checkedTables->contains(table1)) { + localDebug() << "Table" << table1->name() << "already checked"; + return false; // protection against infinite recursion + } + checkedTables->insert(table1); + localDebug() << "Checking if table" << table1->name() << "depends on table" << table2->name(); + if (table1 == table2) { + localDebug() << "Yes"; + return true; + } + for (KDbLookupFieldSchema *lookup : table1->lookupFields()) { + switch (lookup->recordSource().type()) { + case KDbLookupFieldSchemaRecordSource::Type::Table: { + const KDbTableSchema *sourceTable + = conn->tableSchema(lookup->recordSource().name()); + if (sourceTable + && tableDependsOnTable(checkedTables, checkedQueries, conn, sourceTable, table2)) + { + return true; + } + break; + } + case KDbLookupFieldSchemaRecordSource::Type::Query: { + const KDbQuerySchema *sourceQuery + = conn->querySchema(lookup->recordSource().name()); + if (sourceQuery + && queryDependsOnTable(checkedTables, checkedQueries, conn, sourceQuery, table2)) + { + return true; + } + break; + } + default: + kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName(); + //! @todo support more record source types + break; + } + } + return false; + } + + //! Returns @c true if @a table depends on @a query, that is, if: + //! - @a table has lookup columns that reference @a query + static bool tableDependsOnQuery(QSet *checkedTables, + QSet *checkedQueries, + KDbConnection *conn, const KDbTableSchema *table, + const KDbQuerySchema *query) + { + if (checkedTables->contains(table)) { + localDebug() << "Table" << table->name() << "already checked"; + return false; // protection against infinite recursion + } + checkedTables->insert(table); + localDebug() << "Checking if table" << table->name() << "depends on query" << query->name(); + for (KDbLookupFieldSchema *lookup : table->lookupFields()) { + switch (lookup->recordSource().type()) { + case KDbLookupFieldSchemaRecordSource::Type::Table: { + const KDbTableSchema *sourceTable + = conn->tableSchema(lookup->recordSource().name()); + if (sourceTable + && tableDependsOnQuery(checkedTables, checkedQueries, conn, sourceTable, query)) + { + return true; + } + break; + } + case KDbLookupFieldSchemaRecordSource::Type::Query: { + const KDbQuerySchema *sourceQuery + = conn->querySchema(lookup->recordSource().name()); + if (sourceQuery + && queryDependsOnQuery(checkedTables, checkedQueries, conn, sourceQuery, query)) + { + return true; + } + break; + } + default: + kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName(); + //! @todo support more record source types + } + } + return false; + } + + //! Returns @c true if @a query depends on @a table, that is, if: + //! - @a query references table that depends on @a table (dependency is checked using + //! tableDependsOnTable()) + static bool queryDependsOnTable(QSet *checkedTables, + QSet *checkedQueries, + KDbConnection *conn, const KDbQuerySchema *query, + const KDbTableSchema *table) + { + if (checkedQueries->contains(query)) { + localDebug() << "Query" << query->name() << "already checked"; + return false; // protection against infinite recursion + } + checkedQueries->insert(query); + localDebug() << "Checking if query" << query->name() << "depends on table" << table->name(); + for (const KDbTableSchema *queryTable : *query->tables()) { + if (tableDependsOnTable(checkedTables, checkedQueries, conn, queryTable, table)) { + return true; + } + } + return false; + } + + //! Returns @c true if @a query1 depends on @a query2, that is, if: + //! - @a query1 == @a query2, or + //! - @a query2 references table that depends on @a query (dependency is checked using + //! tableDependsOnQuery()) + static bool queryDependsOnQuery(QSet *checkedTables, + QSet *checkedQueries, + KDbConnection *conn, const KDbQuerySchema *query1, + const KDbQuerySchema *query2) + { + if (checkedQueries->contains(query1)) { + localDebug() << "Query" << query1->name() << "already checked"; + return false; // protection against infinite recursion + } + checkedQueries->insert(query1); + localDebug() << "Checking if query" << query1->name() << "depends on query" << query2->name(); + if (query1 == query2) { + localDebug() << "Yes"; + return true; + } + for (const KDbTableSchema *queryTable : *query1->tables()) { + if (tableDependsOnQuery(checkedTables, checkedQueries, conn, queryTable, query2)) { + return true; + } + } + return false; + } + + //! Inserts to @a *result all listeners that listen to changes in table @a table and other tables + //! or queries depending on @a table. + static void collectListeners(QSet *result, + KDbConnection *conn, + const KDbTableSchema *table) + { + Q_ASSERT(result); + Q_ASSERT(conn); + Q_ASSERT(table); + // for all tables with listeners: + for (QHash* >::ConstIterator it( + conn->d->tableSchemaChangeListeners.constBegin()); + it != conn->d->tableSchemaChangeListeners.constEnd(); ++it) + { + // check if it depends on our table + QSet checkedTables; + QSet checkedQueries; + if (tableDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) { + QSet* set = it.value(); + result->unite(*set); + } + } + // for all queries with listeners: + for (QHash* >::ConstIterator it( + conn->d->queryTableSchemaChangeListeners.constBegin()); + it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it) + { + // check if it depends on our table + QSet checkedTables; + QSet checkedQueries; + if (queryDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) { + QSet* set = it.value(); + result->unite(*set); + } + } + } + + //! Inserts to @a *result all listeners that listen to changes in query @a table and other tables + //! or queries depending on @a query. + static void collectListeners(QSet *result, + KDbConnection *conn, + const KDbQuerySchema *query) + { + Q_ASSERT(result); + Q_ASSERT(conn); + Q_ASSERT(query); + QSet* set = conn->d->queryTableSchemaChangeListeners.value(query); + if (set) { + result->unite(*set); + } + // for all tables with listeners: + for (QHash* >::ConstIterator it( + conn->d->tableSchemaChangeListeners.constBegin()); + it != conn->d->tableSchemaChangeListeners.constEnd(); ++it) + { + // check if it depends on our query + QSet checkedTables; + QSet checkedQueries; + if (tableDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) { + QSet* set = it.value(); + result->unite(*set); + } + } + // for all queries with listeners: + for (QHash* >::ConstIterator it( + conn->d->queryTableSchemaChangeListeners.constBegin()); + it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it) + { + // check if it depends on our query + QSet checkedTables; + QSet checkedQueries; + if (queryDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) { + QSet* set = it.value(); + result->unite(*set); + } + } + } + QString name; - Q_DISABLE_COPY(Private) + Q_DISABLE_COPY(KDbTableSchemaChangeListenerPrivate) }; KDbTableSchemaChangeListener::KDbTableSchemaChangeListener() - : d(new Private) + : d(new KDbTableSchemaChangeListenerPrivate) { } @@ -54,59 +371,201 @@ KDbTableSchemaChangeListener* listener, const KDbTableSchema* table) { - QSet* listeners = conn->d->tableSchemaChangeListeners.value(table); - if (!listeners) { - listeners = new QSet(); - conn->d->tableSchemaChangeListeners.insert(table, listeners); + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!listener) { + kdbWarning() << "Missing listener"; + return; + } + if (!table) { + kdbWarning() << "Missing table"; + return; + } + KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, table); +} + +// static +void KDbTableSchemaChangeListener::registerForChanges(KDbConnection *conn, + KDbTableSchemaChangeListener *listener, + const KDbQuerySchema *query) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!listener) { + kdbWarning() << "Missing listener"; + return; + } + if (!query) { + kdbWarning() << "Missing query"; + return; } - listeners->insert(listener); + KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, query); } // static void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener, const KDbTableSchema* table) { - QSet* listeners = conn->d->tableSchemaChangeListeners.value(table); - if (!listeners) + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!listener) { + kdbWarning() << "Missing listener"; return; - listeners->remove(listener); + } + if (!table) { + kdbWarning() << "Missing table"; + return; + } + KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, table); } // static void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn, - KDbTableSchemaChangeListener* listener) + const KDbTableSchema* table) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!table) { + kdbWarning() << "Missing table"; + return; + } + KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, table); +} + +void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn, + KDbTableSchemaChangeListener *listener, + const KDbQuerySchema *query) { - foreach(QSet *listeners, conn->d->tableSchemaChangeListeners) { - listeners->remove(listener); + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!listener) { + kdbWarning() << "Missing listener"; + return; + } + if (!query) { + kdbWarning() << "Missing query"; + return; } + KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, query); +} + +// static +void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn, + const KDbQuerySchema *query) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!query) { + kdbWarning() << "Missing query"; + return; + } + KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, query); +} + +// static +void KDbTableSchemaChangeListener::unregisterForChanges( + KDbConnection *conn, KDbTableSchemaChangeListener* listener) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return; + } + if (!listener) { + kdbWarning() << "Missing listener"; + return; + } + KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener); } // static QList KDbTableSchemaChangeListener::listeners( - const KDbConnection *conn, const KDbTableSchema* table) + KDbConnection *conn, const KDbTableSchema* table) { - //kdbDebug() << d->tableSchemaChangeListeners.count(); - QSet* set = conn->d->tableSchemaChangeListeners.value(table); - return set ? set->toList() : QList(); + if (!conn) { + kdbWarning() << "Missing connection"; + return QList(); + } + if (!table) { + kdbWarning() << "Missing table"; + return QList(); + } + QSet result; + KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, table); + return result.toList(); } // static -tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn, const KDbTableSchema* table) +QList KDbTableSchemaChangeListener::listeners( + KDbConnection *conn, const KDbQuerySchema *query) { - QSet *listeners = conn->d->tableSchemaChangeListeners.value(table); - if (!listeners) { - return true; + if (!conn) { + kdbWarning() << "Missing connection"; + return QList(); + } + if (!query) { + kdbWarning() << "Missing query"; + return QList(); } + QSet result; + KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, query); + return result.toList(); +} - //try to close every window - tristate res = true; - QList list(listeners->toList()); - foreach (KDbTableSchemaChangeListener* listener, list) { +// static +tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn, + const KDbTableSchema *table, const QList &except) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return false; + } + if (!table) { + kdbWarning() << "Missing table"; + return false; + } + QSet toClose(listeners(conn, table).toSet().subtract(except.toSet())); + tristate result = true; + for (KDbTableSchemaChangeListener *listener : toClose) { + const tristate localResult = listener->closeListener(); + if (localResult != true) { + result = localResult; + } + } + return result; +} + +// static +tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn, + const KDbQuerySchema *query, const QList &except) +{ + if (!conn) { + kdbWarning() << "Missing connection"; + return false; + } + if (!query) { + kdbWarning() << "Missing query"; + return false; + } + QSet toClose(listeners(conn, query).toSet().subtract(except.toSet())); + tristate result = true; + for (KDbTableSchemaChangeListener *listener : toClose) { const tristate localResult = listener->closeListener(); if (localResult != true) { - res = localResult; + result = localResult; } } - return res; + return result; } diff --git a/src/config-kdb.h.cmake b/src/config-kdb.h.cmake --- a/src/config-kdb.h.cmake +++ b/src/config-kdb.h.cmake @@ -61,6 +61,11 @@ //! @since 3.1 #cmakedefine KDB_TRANSACTIONS_DEBUG +//! @def KDB_TABLESCHEMACHANGELISTENER_DEBUG +//! @brief Debugging of the KDbTableSchemaChangeListener class +//! @since 3.1 +#cmakedefine KDB_TABLESCHEMACHANGELISTENER_DEBUG + //! @def KDB_DEBUG_GUI //! @brief Defined if a GUI for debugging is enabled #cmakedefine KDB_DEBUG_GUI