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