1 /* This file is part of the KDE project
2    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
3 
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this program; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "KDbTableSchemaChangeListener.h"
21 #include "KDbConnection.h"
22 #include "KDbConnection_p.h"
23 #include "KDbLookupFieldSchema.h"
24 #include "kdb_debug.h"
25 
26 #ifdef KDB_TABLESCHEMACHANGELISTENER_DEBUG
27 # define localDebug(...) kdbDebug(__VA_ARGS__)
28 #else
29 # define localDebug(...) if (true) {} else kdbDebug(__VA_ARGS__)
30 #endif
31 
32 class KDbTableSchemaChangeListenerPrivate
33 {
34 public:
KDbTableSchemaChangeListenerPrivate()35     KDbTableSchemaChangeListenerPrivate()
36     {
37     }
38 
39     //! Registers listener @a listener for changes in table @a table
registerForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbTableSchema * table)40     static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
41                                    const KDbTableSchema *table)
42     {
43         Q_ASSERT(conn);
44         Q_ASSERT(listener);
45         Q_ASSERT(table);
46         QSet<KDbTableSchemaChangeListener*>* listeners = conn->d->tableSchemaChangeListeners.value(table);
47         if (!listeners) {
48             listeners = new QSet<KDbTableSchemaChangeListener*>();
49             conn->d->tableSchemaChangeListeners.insert(table, listeners);
50         }
51         localDebug() << "listener=" << listener << listener->name() << "table=" << table << table->name();
52         listeners->insert(listener);
53     }
54 
55     //! Registers listener @a listener for changes in query @a query
registerForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbQuerySchema * query)56     static void registerForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
57                                    const KDbQuerySchema *query)
58     {
59         Q_ASSERT(conn);
60         Q_ASSERT(listener);
61         Q_ASSERT(query);
62         QSet<KDbTableSchemaChangeListener *> *listeners
63             = conn->d->queryTableSchemaChangeListeners.value(query);
64         if (!listeners) {
65             listeners = new QSet<KDbTableSchemaChangeListener*>();
66             conn->d->queryTableSchemaChangeListeners.insert(query, listeners);
67         }
68         localDebug() << "listener=" << listener->name() << "query=" << query->name();
69         listeners->insert(listener);
70     }
71 
72     //! Unregisters listener @a listener for changes in table @a table
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbTableSchema * table)73     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
74                                      const KDbTableSchema *table)
75     {
76         Q_ASSERT(conn);
77         Q_ASSERT(table);
78         QSet<KDbTableSchemaChangeListener *> *listeners
79             = conn->d->tableSchemaChangeListeners.value(table);
80         if (!listeners) {
81             return;
82         }
83         localDebug() << "listener=" << listener << (listener ? listener->name() : QString::fromLatin1("<all>"))
84                      << "table=" << table << table->name();
85         if (listener) {
86             listeners->remove(listener);
87         } else {
88             delete conn->d->tableSchemaChangeListeners.take(table);
89         }
90     }
91 
92     //! Unregisters listener @a listener for changes in query @a query
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbQuerySchema * query)93     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener *listener,
94                                      const KDbQuerySchema *query)
95     {
96         Q_ASSERT(conn);
97         Q_ASSERT(query);
98         QSet<KDbTableSchemaChangeListener *> *listeners
99             = conn->d->queryTableSchemaChangeListeners.value(query);
100         if (!listeners) {
101             return;
102         }
103         localDebug() << "listener=" << (listener ? listener->name() : QString::fromLatin1("<all>"))
104                      << "query=" << query->name();
105         if (listener) {
106             listeners->remove(listener);
107         } else {
108             listeners->clear();
109         }
110     }
111 
112     //! Unregisters listener @a listener for any changes
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener)113     static void unregisterForChanges(KDbConnection *conn, KDbTableSchemaChangeListener* listener)
114     {
115         Q_ASSERT(conn);
116         Q_ASSERT(listener);
117         localDebug() << "listener=" << listener->name();
118         for (QSet<KDbTableSchemaChangeListener*> *listeners : conn->d->tableSchemaChangeListeners) {
119             listeners->remove(listener);
120         }
121         for (QSet<KDbTableSchemaChangeListener*> *listeners : conn->d->queryTableSchemaChangeListeners) {
122             listeners->remove(listener);
123         }
124     }
125 
126     //! Returns @c true if @a table1 depends on @a table2, that is, if:
127     //! - @a table1 == @a table2, or
128     //! - @a table1 has lookup columns that reference @a table2
tableDependsOnTable(QSet<const KDbTableSchema * > * checkedTables,QSet<const KDbQuerySchema * > * checkedQueries,KDbConnection * conn,const KDbTableSchema * table1,const KDbTableSchema * table2)129     static bool tableDependsOnTable(QSet<const KDbTableSchema *> *checkedTables,
130                                     QSet<const KDbQuerySchema *> *checkedQueries,
131                                     KDbConnection *conn, const KDbTableSchema *table1,
132                                     const KDbTableSchema *table2)
133     {
134         if (checkedTables->contains(table1)) {
135             localDebug() << "Table" << table1 << table1->name() << "already checked";
136             return false; // protection against infinite recursion
137         }
138         checkedTables->insert(table1);
139         localDebug() << "Checking if table" << table1 << table1->name() << "depends on table"
140                      << table2 << table2->name();
141         if (table1 == table2) {
142             localDebug() << "Yes";
143             return true;
144         }
145         for (KDbLookupFieldSchema *lookup : table1->lookupFields()) {
146             switch (lookup->recordSource().type()) {
147             case KDbLookupFieldSchemaRecordSource::Type::Table: {
148                 const KDbTableSchema *sourceTable
149                     = conn->tableSchema(lookup->recordSource().name());
150                 if (sourceTable
151                     && tableDependsOnTable(checkedTables, checkedQueries, conn, sourceTable, table2))
152                 {
153                     return true;
154                 }
155                 break;
156             }
157             case KDbLookupFieldSchemaRecordSource::Type::Query: {
158                 const KDbQuerySchema *sourceQuery
159                     = conn->querySchema(lookup->recordSource().name());
160                 if (sourceQuery
161                     && queryDependsOnTable(checkedTables, checkedQueries, conn, sourceQuery, table2))
162                 {
163                     return true;
164                 }
165                 break;
166             }
167             default:
168                 kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName();
169                 //! @todo support more record source types
170                 break;
171             }
172         }
173         localDebug() << "No";
174         return false;
175     }
176 
177     //! Returns @c true if @a table depends on @a query, that is, if:
178     //! - @a table has lookup columns that reference @a query
tableDependsOnQuery(QSet<const KDbTableSchema * > * checkedTables,QSet<const KDbQuerySchema * > * checkedQueries,KDbConnection * conn,const KDbTableSchema * table,const KDbQuerySchema * query)179     static bool tableDependsOnQuery(QSet<const KDbTableSchema *> *checkedTables,
180                                     QSet<const KDbQuerySchema *> *checkedQueries,
181                                     KDbConnection *conn, const KDbTableSchema *table,
182                                     const KDbQuerySchema *query)
183     {
184         if (checkedTables->contains(table)) {
185             localDebug() << "Table" << table->name() << "already checked";
186             return false; // protection against infinite recursion
187         }
188         checkedTables->insert(table);
189         localDebug() << "Checking if table" << table->name() << "depends on query" << query->name();
190         for (KDbLookupFieldSchema *lookup : table->lookupFields()) {
191             switch (lookup->recordSource().type()) {
192             case KDbLookupFieldSchemaRecordSource::Type::Table: {
193                 const KDbTableSchema *sourceTable
194                     = conn->tableSchema(lookup->recordSource().name());
195                 if (sourceTable
196                     && tableDependsOnQuery(checkedTables, checkedQueries, conn, sourceTable, query))
197                 {
198                     return true;
199                 }
200                 break;
201             }
202             case KDbLookupFieldSchemaRecordSource::Type::Query: {
203                 const KDbQuerySchema *sourceQuery
204                     = conn->querySchema(lookup->recordSource().name());
205                 if (sourceQuery
206                     && queryDependsOnQuery(checkedTables, checkedQueries, conn, sourceQuery, query))
207                 {
208                     return true;
209                 }
210                 break;
211             }
212             default:
213                 kdbWarning() << "Unsupported lookup field's source type" << lookup->recordSource().typeName();
214                 //! @todo support more record source types
215             }
216         }
217         return false;
218     }
219 
220     //! Returns @c true if @a query depends on @a table, that is, if:
221     //! - @a query references table that depends on @a table (dependency is checked using
222     //!   tableDependsOnTable())
queryDependsOnTable(QSet<const KDbTableSchema * > * checkedTables,QSet<const KDbQuerySchema * > * checkedQueries,KDbConnection * conn,const KDbQuerySchema * query,const KDbTableSchema * table)223     static bool queryDependsOnTable(QSet<const KDbTableSchema *> *checkedTables,
224                                     QSet<const KDbQuerySchema *> *checkedQueries,
225                                     KDbConnection *conn, const KDbQuerySchema *query,
226                                     const KDbTableSchema *table)
227     {
228         if (checkedQueries->contains(query)) {
229             localDebug() << "Query" << query->name() << "already checked";
230             return false; // protection against infinite recursion
231         }
232         checkedQueries->insert(query);
233         localDebug() << "Checking if query" << query->name() << "depends on table" << table->name();
234         for (const KDbTableSchema *queryTable : *query->tables()) {
235             if (tableDependsOnTable(checkedTables, checkedQueries, conn, queryTable, table)) {
236                 return true;
237             }
238         }
239         return false;
240     }
241 
242     //! Returns @c true if @a query1 depends on @a query2, that is, if:
243     //! - @a query1 == @a query2, or
244     //! - @a query2 references table that depends on @a query (dependency is checked using
245     //!   tableDependsOnQuery())
queryDependsOnQuery(QSet<const KDbTableSchema * > * checkedTables,QSet<const KDbQuerySchema * > * checkedQueries,KDbConnection * conn,const KDbQuerySchema * query1,const KDbQuerySchema * query2)246     static bool queryDependsOnQuery(QSet<const KDbTableSchema *> *checkedTables,
247                                     QSet<const KDbQuerySchema *> *checkedQueries,
248                                     KDbConnection *conn, const KDbQuerySchema *query1,
249                                     const KDbQuerySchema *query2)
250     {
251         if (checkedQueries->contains(query1)) {
252             localDebug() << "Query" << query1->name() << "already checked";
253             return false; // protection against infinite recursion
254         }
255         checkedQueries->insert(query1);
256         localDebug() << "Checking if query" << query1->name() << "depends on query" << query2->name();
257         if (query1 == query2) {
258             localDebug() << "Yes";
259             return true;
260         }
261         for (const KDbTableSchema *queryTable : *query1->tables()) {
262             if (tableDependsOnQuery(checkedTables, checkedQueries, conn, queryTable, query2)) {
263                 return true;
264             }
265         }
266         return false;
267     }
268 
269     //! Inserts to @a *result all listeners that listen to changes in table @a table and other tables
270     //! or queries depending on @a table.
collectListeners(QSet<KDbTableSchemaChangeListener * > * result,KDbConnection * conn,const KDbTableSchema * table)271     static void collectListeners(QSet<KDbTableSchemaChangeListener *> *result,
272                                  KDbConnection *conn,
273                                  const KDbTableSchema *table)
274     {
275         Q_ASSERT(result);
276         Q_ASSERT(conn);
277         Q_ASSERT(table);
278         // for all tables with listeners:
279         for (QHash<const KDbTableSchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
280                  conn->d->tableSchemaChangeListeners.constBegin());
281              it != conn->d->tableSchemaChangeListeners.constEnd(); ++it)
282         {
283             // check if it depends on our table
284             QSet<const KDbTableSchema *> checkedTables;
285             QSet<const KDbQuerySchema *> checkedQueries;
286             if (tableDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) {
287                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
288                 result->unite(*set);
289             }
290         }
291         // for all queries with listeners:
292         for (QHash<const KDbQuerySchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
293                  conn->d->queryTableSchemaChangeListeners.constBegin());
294              it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it)
295         {
296             // check if it depends on our table
297             QSet<const KDbTableSchema *> checkedTables;
298             QSet<const KDbQuerySchema *> checkedQueries;
299             if (queryDependsOnTable(&checkedTables, &checkedQueries, conn, it.key(), table)) {
300                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
301                 result->unite(*set);
302             }
303         }
304     }
305 
306     //! Inserts to @a *result all listeners that listen to changes in query @a table and other tables
307     //! or queries depending on @a query.
collectListeners(QSet<KDbTableSchemaChangeListener * > * result,KDbConnection * conn,const KDbQuerySchema * query)308     static void collectListeners(QSet<KDbTableSchemaChangeListener *> *result,
309                                  KDbConnection *conn,
310                                  const KDbQuerySchema *query)
311     {
312         Q_ASSERT(result);
313         Q_ASSERT(conn);
314         Q_ASSERT(query);
315         QSet<KDbTableSchemaChangeListener*>* set = conn->d->queryTableSchemaChangeListeners.value(query);
316         if (set) {
317             result->unite(*set);
318         }
319         // for all tables with listeners:
320         for (QHash<const KDbTableSchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
321                  conn->d->tableSchemaChangeListeners.constBegin());
322              it != conn->d->tableSchemaChangeListeners.constEnd(); ++it)
323         {
324             // check if it depends on our query
325             QSet<const KDbTableSchema *> checkedTables;
326             QSet<const KDbQuerySchema *> checkedQueries;
327             if (tableDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) {
328                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
329                 result->unite(*set);
330             }
331         }
332         // for all queries with listeners:
333         for (QHash<const KDbQuerySchema*, QSet<KDbTableSchemaChangeListener*>* >::ConstIterator it(
334                  conn->d->queryTableSchemaChangeListeners.constBegin());
335              it != conn->d->queryTableSchemaChangeListeners.constEnd(); ++it)
336         {
337             // check if it depends on our query
338             QSet<const KDbTableSchema *> checkedTables;
339             QSet<const KDbQuerySchema *> checkedQueries;
340             if (queryDependsOnQuery(&checkedTables, &checkedQueries, conn, it.key(), query)) {
341                 QSet<KDbTableSchemaChangeListener*>* set = it.value();
342                 result->unite(*set);
343             }
344         }
345     }
346 
347     QString name;
348     Q_DISABLE_COPY(KDbTableSchemaChangeListenerPrivate)
349 };
350 
KDbTableSchemaChangeListener()351 KDbTableSchemaChangeListener::KDbTableSchemaChangeListener()
352  : d(new KDbTableSchemaChangeListenerPrivate)
353 {
354 }
355 
~KDbTableSchemaChangeListener()356 KDbTableSchemaChangeListener::~KDbTableSchemaChangeListener()
357 {
358     delete d;
359 }
360 
name() const361 QString KDbTableSchemaChangeListener::name() const
362 {
363     return d->name;
364 }
365 
setName(const QString & name)366 void KDbTableSchemaChangeListener::setName(const QString &name)
367 {
368     d->name = name;
369 }
370 
371 // static
registerForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbTableSchema * table)372 void KDbTableSchemaChangeListener::registerForChanges(KDbConnection *conn,
373                                                       KDbTableSchemaChangeListener* listener,
374                                                       const KDbTableSchema* table)
375 {
376     if (!conn) {
377         kdbWarning() << "Missing connection";
378         return;
379     }
380     if (!listener) {
381         kdbWarning() << "Missing listener";
382         return;
383     }
384     if (!table) {
385         kdbWarning() << "Missing table";
386         return;
387     }
388     KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, table);
389 }
390 
391 // static
registerForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbQuerySchema * query)392 void KDbTableSchemaChangeListener::registerForChanges(KDbConnection *conn,
393                                                       KDbTableSchemaChangeListener *listener,
394                                                       const KDbQuerySchema *query)
395 {
396     if (!conn) {
397         kdbWarning() << "Missing connection";
398         return;
399     }
400     if (!listener) {
401         kdbWarning() << "Missing listener";
402         return;
403     }
404     if (!query) {
405         kdbWarning() << "Missing query";
406         return;
407     }
408     KDbTableSchemaChangeListenerPrivate::registerForChanges(conn, listener, query);
409 }
410 
411 // static
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbTableSchema * table)412 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
413                                          KDbTableSchemaChangeListener* listener,
414                                          const KDbTableSchema* table)
415 {
416     if (!conn) {
417         kdbWarning() << "Missing connection";
418         return;
419     }
420     if (!listener) {
421         kdbWarning() << "Missing listener";
422         return;
423     }
424     if (!table) {
425         kdbWarning() << "Missing table";
426         return;
427     }
428     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, table);
429 }
430 
431 // static
unregisterForChanges(KDbConnection * conn,const KDbTableSchema * table)432 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
433                                                         const KDbTableSchema* table)
434 {
435     if (!conn) {
436         kdbWarning() << "Missing connection";
437         return;
438     }
439     if (!table) {
440         kdbWarning() << "Missing table";
441         return;
442     }
443     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, table);
444 }
445 
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener,const KDbQuerySchema * query)446 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
447                                                         KDbTableSchemaChangeListener *listener,
448                                                         const KDbQuerySchema *query)
449 {
450     if (!conn) {
451         kdbWarning() << "Missing connection";
452         return;
453     }
454     if (!listener) {
455         kdbWarning() << "Missing listener";
456         return;
457     }
458     if (!query) {
459         kdbWarning() << "Missing query";
460         return;
461     }
462     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener, query);
463 }
464 
465 // static
unregisterForChanges(KDbConnection * conn,const KDbQuerySchema * query)466 void KDbTableSchemaChangeListener::unregisterForChanges(KDbConnection *conn,
467                                                         const KDbQuerySchema *query)
468 {
469     if (!conn) {
470         kdbWarning() << "Missing connection";
471         return;
472     }
473     if (!query) {
474         kdbWarning() << "Missing query";
475         return;
476     }
477     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, nullptr, query);
478 }
479 
480 // static
unregisterForChanges(KDbConnection * conn,KDbTableSchemaChangeListener * listener)481 void KDbTableSchemaChangeListener::unregisterForChanges(
482         KDbConnection *conn, KDbTableSchemaChangeListener* listener)
483 {
484     if (!conn) {
485         kdbWarning() << "Missing connection";
486         return;
487     }
488     if (!listener) {
489         kdbWarning() << "Missing listener";
490         return;
491     }
492     KDbTableSchemaChangeListenerPrivate::unregisterForChanges(conn, listener);
493 }
494 
495 // static
listeners(KDbConnection * conn,const KDbTableSchema * table)496 QList<KDbTableSchemaChangeListener*> KDbTableSchemaChangeListener::listeners(
497         KDbConnection *conn, const KDbTableSchema* table)
498 {
499     if (!conn) {
500         kdbWarning() << "Missing connection";
501         return QList<KDbTableSchemaChangeListener*>();
502     }
503     if (!table) {
504         kdbWarning() << "Missing table";
505         return QList<KDbTableSchemaChangeListener*>();
506     }
507     QSet<KDbTableSchemaChangeListener *> result;
508     KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, table);
509     return result.toList();
510 }
511 
512 // static
listeners(KDbConnection * conn,const KDbQuerySchema * query)513 QList<KDbTableSchemaChangeListener*> KDbTableSchemaChangeListener::listeners(
514         KDbConnection *conn, const KDbQuerySchema *query)
515 {
516     if (!conn) {
517         kdbWarning() << "Missing connection";
518         return QList<KDbTableSchemaChangeListener*>();
519     }
520     if (!query) {
521         kdbWarning() << "Missing query";
522         return QList<KDbTableSchemaChangeListener*>();
523     }
524     QSet<KDbTableSchemaChangeListener *> result;
525     KDbTableSchemaChangeListenerPrivate::collectListeners(&result, conn, query);
526     return result.toList();
527 }
528 
529 // static
closeListeners(KDbConnection * conn,const KDbTableSchema * table,const QList<KDbTableSchemaChangeListener * > & except)530 tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn,
531     const KDbTableSchema *table, const QList<KDbTableSchemaChangeListener *> &except)
532 {
533     if (!conn) {
534         kdbWarning() << "Missing connection";
535         return false;
536     }
537     if (!table) {
538         kdbWarning() << "Missing table";
539         return false;
540     }
541     QSet<KDbTableSchemaChangeListener*> toClose(listeners(conn, table).toSet().subtract(except.toSet()));
542     tristate result = true;
543     for (KDbTableSchemaChangeListener *listener : toClose) {
544         const tristate localResult = listener->closeListener();
545         if (localResult != true) {
546             result = localResult;
547         }
548     }
549     return result;
550 }
551 
552 // static
closeListeners(KDbConnection * conn,const KDbQuerySchema * query,const QList<KDbTableSchemaChangeListener * > & except)553 tristate KDbTableSchemaChangeListener::closeListeners(KDbConnection *conn,
554     const KDbQuerySchema *query, const QList<KDbTableSchemaChangeListener *> &except)
555 {
556     if (!conn) {
557         kdbWarning() << "Missing connection";
558         return false;
559     }
560     if (!query) {
561         kdbWarning() << "Missing query";
562         return false;
563     }
564     QSet<KDbTableSchemaChangeListener*> toClose(listeners(conn, query).toSet().subtract(except.toSet()));
565     tristate result = true;
566     for (KDbTableSchemaChangeListener *listener : toClose) {
567         const tristate localResult = listener->closeListener();
568         if (localResult != true) {
569             result = localResult;
570         }
571     }
572     return result;
573 }
574