File indexing completed on 2024-12-08 07:18:25

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This program is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this program; see the file COPYING.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KDbTableSchemaChangeListener.h"
0021 #include "KDbConnection.h"
0022 #include "KDbConnection_p.h"
0023 #include "KDbLookupFieldSchema.h"
0024 #include "kdb_debug.h"
0025 
0026 #ifdef KDB_TABLESCHEMACHANGELISTENER_DEBUG
0027 # define localDebug(...) kdbDebug(__VA_ARGS__)
0028 #else
0029 # define localDebug(...) if (true) {} else kdbDebug(__VA_ARGS__)
0030 #endif
0031 
0032 class KDbTableSchemaChangeListenerPrivate
0033 {
0034 public:
0035     KDbTableSchemaChangeListenerPrivate()
0036     {
0037     }
0038 
0039     //! Registers listener @a listener for changes in table @a table
0040     static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
0041                                    const KDbTableSchema *table)
0042     {
0043         Q_ASSERT(conn);
0044         Q_ASSERT(listener);
0045         Q_ASSERT(table);
0046         QSet<KDbTableSchemaChangeListener*>* listeners = conn->d->tableSchemaChangeListeners.value(table);
0047         if (!listeners) {
0048             listeners = new QSet<KDbTableSchemaChangeListener*>();
0049             conn->d->tableSchemaChangeListeners.insert(table, listeners);
0050         }
0051         localDebug() << "listener=" << listener << listener->name() << "table=" << table << table->name();
0052         listeners->insert(listener);
0053     }
0054 
0055     //! Registers listener @a listener for changes in query @a query
0056     static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
0057                                    const KDbQuerySchema *query)
0058     {
0059         Q_ASSERT(conn);
0060         Q_ASSERT(listener);
0061         Q_ASSERT(query);
0062         QSet<KDbTableSchemaChangeListener *> *listeners
0063             = conn->d->queryTableSchemaChangeListeners.value(query);
0064         if (!listeners) {
0065             listeners = new QSet<KDbTableSchemaChangeListener*>();
0066             conn->d->queryTableSchemaChangeListeners.insert(query, listeners);
0067         }
0068         localDebug() << "listener=" << listener->name() << "query=" << query->name();
0069         listeners->insert(listener);
0070     }
0071 
0072     //! Unregisters listener @a listener for changes in table @a table
0073     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
0074                                      const KDbTableSchema *table)
0075     {
0076         Q_ASSERT(conn);
0077         Q_ASSERT(table);
0078         QSet<KDbTableSchemaChangeListener *> *listeners
0079             = conn->d->tableSchemaChangeListeners.value(table);
0080         if (!listeners) {
0081             return;
0082         }
0083         localDebug() << "listener=" << listener << (listener ? listener->name() : QString::fromLatin1("<all>"))
0084                      << "table=" << table << table->name();
0085         if (listener) {
0086             listeners->remove(listener);
0087         } else {
0088             delete conn->d->tableSchemaChangeListeners.take(table);
0089         }
0090     }
0091 
0092     //! Unregisters listener @a listener for changes in query @a query
0093     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
0094                                      const KDbQuerySchema *query)
0095     {
0096         Q_ASSERT(conn);
0097         Q_ASSERT(query);
0098         QSet<KDbTableSchemaChangeListener *> *listeners
0099             = conn->d->queryTableSchemaChangeListeners.value(query);
0100         if (!listeners) {
0101             return;
0102         }
0103         localDebug() << "listener=" << (listener ? listener->name() : QString::fromLatin1("<all>"))
0104                      << "query=" << query->name();
0105         if (listener) {
0106             listeners->remove(listener);
0107         } else {
0108             listeners->clear();
0109         }
0110     }
0111 
0112     //! Unregisters listener @a listener for any changes
0113     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener)
0114     {
0115         Q_ASSERT(conn);
0116         Q_ASSERT(listener);
0117         localDebug() << "listener=" << listener->name();
0118         for (QSet<KDbTableSchemaChangeListener*> *listeners : conn->d->tableSchemaChangeListeners) {
0119             listeners->remove(listener);
0120         }
0121         for (QSet<KDbTableSchemaChangeListener*> *listeners : conn->d->queryTableSchemaChangeListeners) {
0122             listeners->remove(listener);
0123         }
0124     }
0125 
0126     //! Returns @c true if @a table1 depends on @a table2, that is, if:
0127     //! - @a table1 == @a table2, or
0128     //! - @a table1 has lookup columns that reference @a table2
0129     static bool tableDependsOnTable(QSet<const KDbTableSchema *> *checkedTables,
0130                                     QSet<const KDbQuerySchema *> *checkedQueries,
0131                                     KDbConnection *conn, const KDbTableSchema *table1,
0132                                     const KDbTableSchema *table2)
0133     {
0134         if (checkedTables->contains(table1)) {
0135             localDebug() << "Table" << table1 << table1->name() << "already checked";
0136             return false; // protection against infinite recursion
0137         }
0138         checkedTables->insert(table1);
0139         localDebug() << "Checking if table" << table1 << table1->name() << "depends on table"
0140                      << table2 << table2->name();
0141         if (table1 == table2) {
0142             localDebug() << "Yes";
0143             return true;
0144         }
0145         for (KDbLookupFieldSchema *lookup : table1->lookupFields()) {
0146             switch (lookup->recordSource().type()) {
0147             case KDbLookupFieldSchemaRecordSource::Type::Table: {
0148                 const KDbTableSchema *sourceTable
0149                     = conn->tableSchema(lookup->recordSource().name());
0150                 if (sourceTable
0151                     && tableDependsOnTable(checkedTables, checkedQueries, conn, sourceTable, table2))
0152                 {
0153                     return true;
0154                 }
0155                 break;
0156             }
0157             case KDbLookupFieldSchemaRecordSource::Type::Query: {
0158                 const KDbQuerySchema *sourceQuery
0159                     = conn->querySchema(lookup->recordSource().name());
0160                 if (sourceQuery
0161                     && queryDependsOnTable(checkedTables, checkedQueries, conn, sourceQuery, table2))
0162                 {
0163                     return true;
0164                 }
0165                 break;
0166             }
0167             default:
0168                 kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName();
0169                 //! @todo support more record source types
0170                 break;
0171             }
0172         }
0173         localDebug() << "No";
0174         return false;
0175     }
0176 
0177     //! Returns @c true if @a table depends on @a query, that is, if:
0178     //! - @a table has lookup columns that reference @a query
0179     static bool tableDependsOnQuery(QSet<const KDbTableSchema *> *checkedTables,
0180                                     QSet<const KDbQuerySchema *> *checkedQueries,
0181                                     KDbConnection *conn, const KDbTableSchema *table,
0182                                     const KDbQuerySchema *query)
0183     {
0184         if (checkedTables->contains(table)) {
0185             localDebug() << "Table" << table->name() << "already checked";
0186             return false; // protection against infinite recursion
0187         }
0188         checkedTables->insert(table);
0189         localDebug() << "Checking if table" << table->name() << "depends on query" << query->name();
0190         for (KDbLookupFieldSchema *lookup : table->lookupFields()) {
0191             switch (lookup->recordSource().type()) {
0192             case KDbLookupFieldSchemaRecordSource::Type::Table: {
0193                 const KDbTableSchema *sourceTable
0194                     = conn->tableSchema(lookup->recordSource().name());
0195                 if (sourceTable
0196                     && tableDependsOnQuery(checkedTables, checkedQueries, conn, sourceTable, query))
0197                 {
0198                     return true;
0199                 }
0200                 break;
0201             }
0202             case KDbLookupFieldSchemaRecordSource::Type::Query: {
0203                 const KDbQuerySchema *sourceQuery
0204                     = conn->querySchema(lookup->recordSource().name());
0205                 if (sourceQuery
0206                     && queryDependsOnQuery(checkedTables, checkedQueries, conn, sourceQuery, query))
0207                 {
0208                     return true;
0209                 }
0210                 break;
0211             }
0212             default:
0213                 kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName();
0214                 //! @todo support more record source types
0215             }
0216         }
0217         return false;
0218     }
0219 
0220     //! Returns @c true if @a query depends on @a table, that is, if:
0221     //! - @a query references table that depends on @a table (dependency is checked using
0222     //!   tableDependsOnTable())
0223     static bool queryDependsOnTable(QSet<const KDbTableSchema *> *checkedTables,
0224                                     QSet<const KDbQuerySchema *> *checkedQueries,
0225                                     KDbConnection *conn, const KDbQuerySchema *query,
0226                                     const KDbTableSchema *table)
0227     {
0228         if (checkedQueries->contains(query)) {
0229             localDebug() << "Query" << query->name() << "already checked";
0230             return false; // protection against infinite recursion
0231         }
0232         checkedQueries->insert(query);
0233         localDebug() << "Checking if query" << query->name() << "depends on table" << table->name();
0234         for (const KDbTableSchema *queryTable : *query->tables()) {
0235             if (tableDependsOnTable(checkedTables, checkedQueries, conn, queryTable, table)) {
0236                 return true;
0237             }
0238         }
0239         return false;
0240     }
0241 
0242     //! Returns @c true if @a query1 depends on @a query2, that is, if:
0243     //! - @a query1 == @a query2, or
0244     //! - @a query2 references table that depends on @a query (dependency is checked using
0245     //!   tableDependsOnQuery())
0246     static bool queryDependsOnQuery(QSet<const KDbTableSchema *> *checkedTables,
0247                                     QSet<const KDbQuerySchema *> *checkedQueries,
0248                                     KDbConnection *conn, const KDbQuerySchema *query1,
0249                                     const KDbQuerySchema *query2)
0250     {
0251         if (checkedQueries->contains(query1)) {
0252             localDebug() << "Query" << query1->name() << "already checked";
0253             return false; // protection against infinite recursion
0254         }
0255         checkedQueries->insert(query1);
0256         localDebug() << "Checking if query" << query1->name() << "depends on query" << query2->name();
0257         if (query1 == query2) {
0258             localDebug() << "Yes";
0259             return true;
0260         }
0261         for (const KDbTableSchema *queryTable : *query1->tables()) {
0262             if (tableDependsOnQuery(checkedTables, checkedQueries, conn, queryTable, query2)) {
0263                 return true;
0264             }
0265         }
0266         return false;
0267     }
0268 
0269     //! Inserts to @a *result all listeners that listen to changes in table @a table and other tables
0270     //! or queries depending on @a table.
0271     static void collectListeners(QSet<KDbTableSchemaChangeListener *> *result,
0272                                  KDbConnection *conn,
0273                                  const KDbTableSchema *table)
0274     {
0275         Q_ASSERT(result);
0276         Q_ASSERT(conn);
0277         Q_ASSERT(table);
0278         // for all tables with listeners:
0279         for (QHash<const KDbTableSchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
0280                  conn->d->tableSchemaChangeListeners.constBegin());
0281              it != conn->d->tableSchemaChangeListeners.constEnd(); ++it)
0282         {
0283             // check if it depends on our table
0284             QSet<const KDbTableSchema *> checkedTables;
0285             QSet<const KDbQuerySchema *> checkedQueries;
0286             if (tableDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) {
0287                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
0288                 result->unite(*set);
0289             }
0290         }
0291         // for all queries with listeners:
0292         for (QHash<const KDbQuerySchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
0293                  conn->d->queryTableSchemaChangeListeners.constBegin());
0294              it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it)
0295         {
0296             // check if it depends on our table
0297             QSet<const KDbTableSchema *> checkedTables;
0298             QSet<const KDbQuerySchema *> checkedQueries;
0299             if (queryDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) {
0300                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
0301                 result->unite(*set);
0302             }
0303         }
0304     }
0305 
0306     //! Inserts to @a *result all listeners that listen to changes in query @a table and other tables
0307     //! or queries depending on @a query.
0308     static void collectListeners(QSet<KDbTableSchemaChangeListener *> *result,
0309                                  KDbConnection *conn,
0310                                  const KDbQuerySchema *query)
0311     {
0312         Q_ASSERT(result);
0313         Q_ASSERT(conn);
0314         Q_ASSERT(query);
0315         QSet<KDbTableSchemaChangeListener*>* set = conn->d->queryTableSchemaChangeListeners.value(query);
0316         if (set) {
0317             result->unite(*set);
0318         }
0319         // for all tables with listeners:
0320         for (QHash<const KDbTableSchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
0321                  conn->d->tableSchemaChangeListeners.constBegin());
0322              it != conn->d->tableSchemaChangeListeners.constEnd(); ++it)
0323         {
0324             // check if it depends on our query
0325             QSet<const KDbTableSchema *> checkedTables;
0326             QSet<const KDbQuerySchema *> checkedQueries;
0327             if (tableDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) {
0328                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
0329                 result->unite(*set);
0330             }
0331         }
0332         // for all queries with listeners:
0333         for (QHash<const KDbQuerySchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
0334                  conn->d->queryTableSchemaChangeListeners.constBegin());
0335              it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it)
0336         {
0337             // check if it depends on our query
0338             QSet<const KDbTableSchema *> checkedTables;
0339             QSet<const KDbQuerySchema *> checkedQueries;
0340             if (queryDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) {
0341                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
0342                 result->unite(*set);
0343             }
0344         }
0345     }
0346 
0347     QString name;
0348     Q_DISABLE_COPY(KDbTableSchemaChangeListenerPrivate)
0349 };
0350 
0351 KDbTableSchemaChangeListener::KDbTableSchemaChangeListener()
0352  : d(new KDbTableSchemaChangeListenerPrivate)
0353 {
0354 }
0355 
0356 KDbTableSchemaChangeListener::~KDbTableSchemaChangeListener()
0357 {
0358     delete d;
0359 }
0360 
0361 QString KDbTableSchemaChangeListener::name() const
0362 {
0363     return d->name;
0364 }
0365 
0366 void KDbTableSchemaChangeListener::setName(const QString &name)
0367 {
0368     d->name = name;
0369 }
0370 
0371 // static
0372 void KDbTableSchemaChangeListener::registerForChanges(KDbConnection *conn,
0373                                                       KDbTableSchemaChangeListener* listener,
0374                                                       const KDbTableSchema* table)
0375 {
0376     if (!conn) {
0377         kdbWarning() << "Missing connection";
0378         return;
0379     }
0380     if (!listener) {
0381         kdbWarning() << "Missing listener";
0382         return;
0383     }
0384     if (!table) {
0385         kdbWarning() << "Missing table";
0386         return;
0387     }
0388     KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, table);
0389 }
0390 
0391 // static
0392 void KDbTableSchemaChangeListener::registerForChanges(KDbConnection *conn,
0393                                                       KDbTableSchemaChangeListener *listener,
0394                                                       const KDbQuerySchema *query)
0395 {
0396     if (!conn) {
0397         kdbWarning() << "Missing connection";
0398         return;
0399     }
0400     if (!listener) {
0401         kdbWarning() << "Missing listener";
0402         return;
0403     }
0404     if (!query) {
0405         kdbWarning() << "Missing query";
0406         return;
0407     }
0408     KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, query);
0409 }
0410 
0411 // static
0412 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
0413                                          KDbTableSchemaChangeListener* listener,
0414                                          const KDbTableSchema* table)
0415 {
0416     if (!conn) {
0417         kdbWarning() << "Missing connection";
0418         return;
0419     }
0420     if (!listener) {
0421         kdbWarning() << "Missing listener";
0422         return;
0423     }
0424     if (!table) {
0425         kdbWarning() << "Missing table";
0426         return;
0427     }
0428     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, table);
0429 }
0430 
0431 // static
0432 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
0433                                                         const KDbTableSchema* table)
0434 {
0435     if (!conn) {
0436         kdbWarning() << "Missing connection";
0437         return;
0438     }
0439     if (!table) {
0440         kdbWarning() << "Missing table";
0441         return;
0442     }
0443     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, table);
0444 }
0445 
0446 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
0447                                                         KDbTableSchemaChangeListener *listener,
0448                                                         const KDbQuerySchema *query)
0449 {
0450     if (!conn) {
0451         kdbWarning() << "Missing connection";
0452         return;
0453     }
0454     if (!listener) {
0455         kdbWarning() << "Missing listener";
0456         return;
0457     }
0458     if (!query) {
0459         kdbWarning() << "Missing query";
0460         return;
0461     }
0462     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, query);
0463 }
0464 
0465 // static
0466 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
0467                                                         const KDbQuerySchema *query)
0468 {
0469     if (!conn) {
0470         kdbWarning() << "Missing connection";
0471         return;
0472     }
0473     if (!query) {
0474         kdbWarning() << "Missing query";
0475         return;
0476     }
0477     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, query);
0478 }
0479 
0480 // static
0481 void KDbTableSchemaChangeListener::unregisterForChanges(
0482         KDbConnection *conn, KDbTableSchemaChangeListener* listener)
0483 {
0484     if (!conn) {
0485         kdbWarning() << "Missing connection";
0486         return;
0487     }
0488     if (!listener) {
0489         kdbWarning() << "Missing listener";
0490         return;
0491     }
0492     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener);
0493 }
0494 
0495 // static
0496 QList<KDbTableSchemaChangeListener*> KDbTableSchemaChangeListener::listeners(
0497         KDbConnection *conn, const KDbTableSchema* table)
0498 {
0499     if (!conn) {
0500         kdbWarning() << "Missing connection";
0501         return QList<KDbTableSchemaChangeListener*>();
0502     }
0503     if (!table) {
0504         kdbWarning() << "Missing table";
0505         return QList<KDbTableSchemaChangeListener*>();
0506     }
0507     QSet<KDbTableSchemaChangeListener *> result;
0508     KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, table);
0509     return result.values();
0510 }
0511 
0512 // static
0513 QList<KDbTableSchemaChangeListener*> KDbTableSchemaChangeListener::listeners(
0514         KDbConnection *conn, const KDbQuerySchema *query)
0515 {
0516     if (!conn) {
0517         kdbWarning() << "Missing connection";
0518         return QList<KDbTableSchemaChangeListener*>();
0519     }
0520     if (!query) {
0521         kdbWarning() << "Missing query";
0522         return QList<KDbTableSchemaChangeListener*>();
0523     }
0524     QSet<KDbTableSchemaChangeListener *> result;
0525     KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, query);
0526     return result.values();
0527 }
0528 
0529 // static
0530 tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn,
0531     const KDbTableSchema *table, const QList<KDbTableSchemaChangeListener *> &except)
0532 {
0533     if (!conn) {
0534         kdbWarning() << "Missing connection";
0535         return false;
0536     }
0537     if (!table) {
0538         kdbWarning() << "Missing table";
0539         return false;
0540     }
0541     QSet<KDbTableSchemaChangeListener*> toClose(listeners(conn, table).toSet().subtract(except.toSet()));
0542     tristate result = true;
0543     for (KDbTableSchemaChangeListener *listener : qAsConst(toClose)) {
0544         const tristate localResult = listener->closeListener();
0545         if (localResult != true) {
0546             result = localResult;
0547         }
0548     }
0549     return result;
0550 }
0551 
0552 // static
0553 tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn,
0554     const KDbQuerySchema *query, const QList<KDbTableSchemaChangeListener *> &except)
0555 {
0556     if (!conn) {
0557         kdbWarning() << "Missing connection";
0558         return false;
0559     }
0560     if (!query) {
0561         kdbWarning() << "Missing query";
0562         return false;
0563     }
0564     QSet<KDbTableSchemaChangeListener*> toClose(listeners(conn, query).toSet().subtract(except.toSet()));
0565     tristate result = true;
0566     for (KDbTableSchemaChangeListener *listener : qAsConst(toClose)) {
0567         const tristate localResult = listener->closeListener();
0568         if (localResult != true) {
0569             result = localResult;
0570         }
0571     }
0572     return result;
0573 }