File indexing completed on 2024-10-13 12:38:26
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 }