35
35
36
36
37
37
/* *
38
- * Creates a new Database object, immediately creates the table models and initializes the
39
- * SQLite database driver.
38
+ * Creates a new Database object, immediately creates the table models and initializes the SQLite
39
+ * database driver.
40
40
*/
41
41
Database::Database () :
42
42
databaseLoaded(false ),
43
43
tables(QList<Table*>()),
44
+ breadcrumbMatrix(QMap<const NormalTable*, QMap<const Table*, Breadcrumbs>>()),
44
45
mainWindowStatusBar(nullptr )
45
46
{
46
47
tripsTable = new TripsTable ();
@@ -67,6 +68,8 @@ Database::Database() :
67
68
68
69
projectSettings = new ProjectSettings (settingsTable);
69
70
71
+ computeBreadcrumbMatrix ();
72
+
70
73
71
74
QSqlDatabase::addDatabase (" QSQLITE" );
72
75
}
@@ -75,7 +78,7 @@ Database::Database() :
75
78
* Destroys the Database object.
76
79
*/
77
80
Database::~Database () {
78
- qDeleteAll (getTableList () );
81
+ qDeleteAll (tables );
79
82
}
80
83
81
84
@@ -244,6 +247,151 @@ void Database::populateBuffers(QWidget* parent)
244
247
245
248
246
249
250
+ /* *
251
+ * For all pairs of normal tables and any other table (always excluding the project settings table),
252
+ * computes the breadcrumbs for the connection between them and stores them in the breadcrumbMatrix.
253
+ *
254
+ * The algorithm consists of three steps:
255
+ * 1. Fill the diagonal with empty breadcrumbs (since connecting a table to itself does not
256
+ * require any breadcrumbs).
257
+ * 2. Proactively find all trivial connections by iterating through all tables and adding
258
+ * breadcrumbs consisting of a single column pair for each foreign key column.
259
+ * This step adds two breadcrumbs for each normal table and four for each associative table.
260
+ * 3. Iteratively find the remaining connections by chaining existing ones together. During this
261
+ * process, the maximum length of accepted new breadcrumb chains is gradually increased to
262
+ * prevent forming connections which turn back on themselves.
263
+ */
264
+ void Database::computeBreadcrumbMatrix ()
265
+ {
266
+ const int numNormalTables = getNormalItemTableList ().size ();
267
+ const int numCells = numNormalTables * getItemTableList ().size ();
268
+ int numFilled = 0 ;
269
+
270
+ // Fill empty diagonal (normal table to itself)
271
+ for (const NormalTable* const normalTable : getNormalItemTableList ()) {
272
+ breadcrumbMatrix[normalTable][normalTable] = Breadcrumbs ();
273
+ numFilled++;
274
+ }
275
+
276
+ // Fill trivial connections (one crumb or two over an associative table)
277
+ for (const Table* const table : getItemTableList ()) {
278
+ if (table->isAssociative ) {
279
+ // Associative table
280
+ const AssociativeTable* const associativeTable = (AssociativeTable*) table;
281
+ PrimaryForeignKeyColumn* const column1 = associativeTable->getColumn1 ();
282
+ PrimaryForeignKeyColumn* const column2 = associativeTable->getColumn2 ();
283
+ PrimaryKeyColumn* const foreignColumn1 = column1->getReferencedForeignColumn ();
284
+ PrimaryKeyColumn* const foreignColumn2 = column2->getReferencedForeignColumn ();
285
+ const NormalTable* const foreignTable1 = (NormalTable*) foreignColumn1->table ;
286
+ const NormalTable* const foreignTable2 = (NormalTable*) foreignColumn2->table ;
287
+
288
+ assert (breadcrumbMatrix[foreignTable1][table].isEmpty ());
289
+ assert (breadcrumbMatrix[foreignTable2][table].isEmpty ());
290
+ assert (breadcrumbMatrix[foreignTable1][foreignTable2].isEmpty ());
291
+ assert (breadcrumbMatrix[foreignTable2][foreignTable1].isEmpty ());
292
+
293
+ // Foreign table to associative table (either side)
294
+ breadcrumbMatrix[foreignTable1][table] = Breadcrumbs ({
295
+ {foreignColumn1, column1}
296
+ });
297
+ breadcrumbMatrix[foreignTable2][table] = Breadcrumbs ({
298
+ {foreignColumn2, column2}
299
+ });
300
+ // Foreign table to other foreign table via associative table (either side)
301
+ breadcrumbMatrix[foreignTable1][foreignTable2] = Breadcrumbs ({
302
+ {foreignColumn1, column1},
303
+ {column2, foreignColumn2}
304
+ });
305
+ breadcrumbMatrix[foreignTable2][foreignTable1] = Breadcrumbs ({
306
+ {foreignColumn2, column2},
307
+ {column1, foreignColumn1}
308
+ });
309
+ numFilled += 4 ;
310
+ }
311
+
312
+ else {
313
+ // Normal table
314
+ const NormalTable* const normalTable = (NormalTable*) table;
315
+ QList<const Column*> foreignKeyColumns = normalTable->getForeignKeyColumnList ();
316
+ for (const Column* const column : foreignKeyColumns) {
317
+ ForeignKeyColumn* localColumn = (ForeignKeyColumn*) column;
318
+ PrimaryKeyColumn* foreignColumn = column->getReferencedForeignColumn ();
319
+ const NormalTable* const foreignTable = (NormalTable*) foreignColumn->table ;
320
+
321
+ assert (breadcrumbMatrix[normalTable][foreignTable].isEmpty ());
322
+ assert (breadcrumbMatrix[foreignTable][normalTable].isEmpty ());
323
+
324
+ breadcrumbMatrix[normalTable][foreignTable] = Breadcrumbs ({
325
+ {localColumn, foreignColumn}
326
+ });
327
+ breadcrumbMatrix[foreignTable][normalTable] = Breadcrumbs ({
328
+ {foreignColumn, localColumn}
329
+ });
330
+ numFilled += 2 ;
331
+ }
332
+ }
333
+ }
334
+
335
+ // Iteratively find the remaining connections by chaining existing ones
336
+ int connectionLengthLimit = 2 ;
337
+ // Gradually increase the maximum length of combined connections which are accepted to prevent
338
+ // ending up with connections which turn back on themselves.
339
+ while (numFilled < numCells) {
340
+ const int numFilledBeforeIter = numFilled;
341
+
342
+ for (const NormalTable* const tableA : getNormalItemTableList ()) {
343
+ for (const NormalTable* const tableB : getNormalItemTableList ()) {
344
+ if (tableA == tableB) continue ;
345
+ if (breadcrumbMatrix[tableA][tableB].isEmpty ()) continue ;
346
+ // We are on a connection A -> B which has already been found.
347
+ // We are now looking for another existing connection B -> C. We then derive A -> C.
348
+
349
+ for (const Table* const tableC : getItemTableList ()) {
350
+ if (tableB == tableC || tableA == tableC) continue ;
351
+ if (breadcrumbMatrix[tableB][tableC].isEmpty ()) continue ;
352
+ // We have found an existing connection B -> C.
353
+ if (!breadcrumbMatrix[tableA][tableC].isEmpty ()) continue ;
354
+
355
+ const Breadcrumbs& breadcrumbsAtoB = breadcrumbMatrix[tableA][tableB];
356
+ const Breadcrumbs& breadcrumbsBtoC = breadcrumbMatrix[tableB][tableC];
357
+
358
+ int candidateLength = breadcrumbsAtoB.length () + breadcrumbsBtoC.length ();
359
+ if (candidateLength > connectionLengthLimit) continue ;
360
+
361
+ breadcrumbMatrix[tableA][tableC] = breadcrumbsAtoB + breadcrumbsBtoC;
362
+ numFilled++;
363
+ }
364
+ }
365
+ }
366
+
367
+ assert (numFilled > numFilledBeforeIter);
368
+ connectionLengthLimit++;
369
+ }
370
+ }
371
+
372
+ /* *
373
+ * Returns the pre-computed breadcrumbs for the connection between the given tables.
374
+ *
375
+ * @param startTable The table to start from (e.g. where the user made a selection).
376
+ * @param destinationTable The table which must be reached.
377
+ * @return The breadcrumbs for the connection between the given tables.
378
+ */
379
+ Breadcrumbs Database::getBreadcrumbsFor (const NormalTable* startTable, const NormalTable* destinationTable)
380
+ {
381
+ assert (breadcrumbMatrix.contains (startTable));
382
+ assert (breadcrumbMatrix.value (startTable).contains (destinationTable));
383
+
384
+ return breadcrumbMatrix.value (startTable).value (destinationTable);
385
+ }
386
+
387
+
388
+
389
+ /* *
390
+ * Translates the given string under the context "Database".
391
+ *
392
+ * @param string The string to translate.
393
+ * @return The translated string.
394
+ */
247
395
QString Database::tr (const QString& string)
248
396
{
249
397
return QCoreApplication::translate (" Database" , string.toStdString ().c_str ());
@@ -256,9 +404,30 @@ QString Database::tr(const QString& string)
256
404
*
257
405
* @return A list of all regular tables (not project settings) in the database.
258
406
*/
259
- QList<Table*> Database::getTableList () const
407
+ QList<Table*> Database::getItemTableList () const
408
+ {
409
+ QList<Table*> list = QList<Table*>();
410
+ for (Table* const table : tables) {
411
+ if (table == settingsTable) continue ;
412
+ list.append (table);
413
+ }
414
+ return list;
415
+ }
416
+
417
+ /* *
418
+ * Returns a list of all normal tables in the database (not including the project settings table).
419
+ *
420
+ * @return A list of all regular normal tables (not project settings) in the database.
421
+ */
422
+ QList<NormalTable*> Database::getNormalItemTableList () const
260
423
{
261
- return QList<Table*>(tables);
424
+ QList<NormalTable*> list = QList<NormalTable*>();
425
+ for (const Table* const table : tables) {
426
+ if (table == settingsTable) continue ;
427
+ if (table->isAssociative ) continue ;
428
+ list.append ((NormalTable*) table);
429
+ }
430
+ return list;
262
431
}
263
432
264
433
0 commit comments