Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qhelpdbreader.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "qhelp_global.h"
7
8#include <QtCore/qfile.h>
9#include <QtCore/qmap.h>
10#include <QtCore/qvariant.h>
11#include <QtCore/qversionnumber.h>
12#include <QtSql/qsqldatabase.h>
13#include <QtSql/qsqlerror.h>
14#include <QtSql/qsqlquery.h>
15
17
18using namespace Qt::StringLiterals;
19
20QHelpDBReader::QHelpDBReader(const QString &dbName)
21 : m_dbName(dbName)
22 , m_uniqueId(QHelpGlobal::uniquifyConnectionName("QHelpDBReader"_L1, this))
23{}
24
25QHelpDBReader::QHelpDBReader(const QString &dbName, const QString &uniqueId, QObject *parent)
26 : QObject(parent)
27 , m_dbName(dbName)
28 , m_uniqueId(uniqueId)
29{}
30
32{
33 if (m_initDone)
34 QSqlDatabase::removeDatabase(m_uniqueId);
35}
36
38{
39 if (m_initDone)
40 return true;
41
42 if (!QFile::exists(m_dbName))
43 return false;
44
45 if (!initDB()) {
46 QSqlDatabase::removeDatabase(m_uniqueId);
47 return false;
48 }
49
50 m_initDone = true;
51 m_query.reset(new QSqlQuery(QSqlDatabase::database(m_uniqueId)));
52 return true;
53}
54
55bool QHelpDBReader::initDB()
56{
57 QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"_L1, m_uniqueId);
58 db.setConnectOptions("QSQLITE_OPEN_READONLY"_L1);
59 db.setDatabaseName(m_dbName);
60 if (!db.open()) {
61 /*: The placeholders are: %1 - The name of the database which cannot be opened
62 %2 - The unique id for the connection
63 %3 - The actual error string */
64 m_error = tr("Cannot open database \"%1\" \"%2\": %3").arg(m_dbName, m_uniqueId, db.lastError().text());
65 return false;
66 }
67 return true;
68}
69
71{
72 if (!m_namespace.isEmpty())
73 return m_namespace;
74 if (m_query) {
75 m_query->exec("SELECT Name FROM NamespaceTable"_L1);
76 if (m_query->next())
77 m_namespace = m_query->value(0).toString();
78 }
79 return m_namespace;
80}
81
83{
84 if (m_query) {
85 m_query->exec("SELECT Name FROM FolderTable WHERE Id=1"_L1);
86 if (m_query->next())
87 return m_query->value(0).toString();
88 }
89 return {};
90}
91
92QString QHelpDBReader::version() const
93{
94 const QString versionString = metaData("version"_L1).toString();
95 if (versionString.isEmpty())
96 return qtVersionHeuristic();
97 return versionString;
98}
99
101{
102 QString tail;
103 int major = -1;
104 int minor = -1;
105 int patch = -1;
106};
107
108static VersionData getVersionData(const QString &nameSpace)
109{
110 VersionData result;
111 if (!nameSpace.startsWith("org.qt-project."_L1))
112 return result;
113
114 // We take the namespace tail, starting from the last letter in namespace name.
115 // We drop any non digit characters.
116 const QChar dot(u'.');
117 QString &tail = result.tail;
118 for (int i = nameSpace.size(); i > 0; --i) {
119 const QChar c = nameSpace.at(i - 1);
120 if (c.isDigit() || c == dot)
121 tail.prepend(c);
122
123 if (c.isLetter())
124 break;
125 }
126
127 if (!tail.startsWith(dot) && tail.count(dot) == 1) {
128 // The org.qt-project.qtquickcontrols2.5120 case,
129 // tail = 2.5120 here. We need to cut "2." here.
130 const int dotIndex = tail.indexOf(dot);
131 if (dotIndex > 0)
132 tail = tail.mid(dotIndex);
133 }
134
135 // Drop beginning dots
136 while (tail.startsWith(dot))
137 tail = tail.mid(1);
138
139 // Drop ending dots
140 while (tail.endsWith(dot))
141 tail.chop(1);
142
143 if (tail.size() >= 3 && tail.size() <= 5 && tail.count(dot) == 0) {
144 // When we have 3 digits, we split it like: ABC -> A.B.C
145 // When we have 4 digits, we split it like: ABCD -> A.BC.D
146 // When we have 5 digits, we split it like: ABCDE -> A.BC.DE
147 QStringView tail{result.tail};
148 result.major = tail.left(1).toInt();
149 result.minor = tail.size() == 3 ? tail.mid(1, 1).toInt() : tail.mid(1, 2).toInt();
150 result.patch = tail.size() == 5 ? tail.right(2).toInt() : tail.right(1).toInt();
151 }
152 return result;
153}
154
155QString QHelpDBReader::qtVersionHeuristic() const
156{
157 VersionData version = getVersionData(namespaceName());
158 return version.major >= 0
159 ? QString::number(version.major) + u'.' + QString::number(version.minor) + u'.' + QString::number(version.patch)
160 : version.tail;
161}
162
163QVersionNumber QHelpDBReader::versionHeuristic(const QString &namespaceName)
164{
165 VersionData version = getVersionData(namespaceName);
166 return version.major >= 0 ? QVersionNumber{version.major, version.minor, version.patch}
167 : QVersionNumber::fromString(version.tail);
168}
169
170static bool isAttributeUsed(QSqlQuery *query, const QString &tableName, int attributeId)
171{
172 query->prepare(QString::fromLatin1("SELECT FilterAttributeId "
173 "FROM %1 "
174 "WHERE FilterAttributeId = ? "
175 "LIMIT 1").arg(tableName));
176 query->bindValue(0, attributeId);
177 query->exec();
178 return query->next(); // if we got a result it means it was used
179}
180
181static int filterDataCount(QSqlQuery *query, const QString &tableName)
182{
183 query->exec(QString::fromLatin1("SELECT COUNT(*) FROM"
184 "(SELECT DISTINCT * FROM %1)").arg(tableName));
185 query->next();
186 return query->value(0).toInt();
187}
188
190{
191 IndexTable table;
192 if (!m_query)
193 return table;
194
195 QMap<int, QString> attributeIds;
196 m_query->exec("SELECT DISTINCT Id, Name FROM FilterAttributeTable ORDER BY Id"_L1);
197 while (m_query->next())
198 attributeIds.insert(m_query->value(0).toInt(), m_query->value(1).toString());
199
200 // Maybe some are unused and specified erroneously in the named filter only,
201 // like it was in case of qtlocation.qch <= qt 5.9
202 QList<int> usedAttributeIds;
203 for (auto it = attributeIds.cbegin(), end = attributeIds.cend(); it != end; ++it) {
204 const int attributeId = it.key();
205 if (isAttributeUsed(m_query.get(), "IndexFilterTable"_L1, attributeId)
206 || isAttributeUsed(m_query.get(), "ContentsFilterTable"_L1, attributeId)
207 || isAttributeUsed(m_query.get(), "FileFilterTable"_L1, attributeId)) {
208 usedAttributeIds.append(attributeId);
209 }
210 }
211
212 bool legacy = false;
213 m_query->exec("SELECT * FROM pragma_table_info('IndexTable') WHERE name='ContextName'"_L1);
214 if (m_query->next())
215 legacy = true;
216
217 const QString identifierColumnName = legacy ? "ContextName"_L1 : "Identifier"_L1;
218 const int usedAttributeCount = usedAttributeIds.size();
219
220 QMap<int, IndexItem> idToIndexItem;
221 m_query->exec(QString::fromLatin1("SELECT Name, %1, FileId, Anchor, Id "
222 "FROM IndexTable "
223 "ORDER BY Id").arg(identifierColumnName));
224 while (m_query->next()) {
225 IndexItem indexItem;
226 indexItem.name = m_query->value(0).toString();
227 indexItem.identifier = m_query->value(1).toString();
228 indexItem.fileId = m_query->value(2).toInt();
229 indexItem.anchor = m_query->value(3).toString();
230 const int indexId = m_query->value(4).toInt();
231
232 idToIndexItem.insert(indexId, indexItem);
233 }
234
235 QMap<int, FileItem> idToFileItem;
236 QMap<int, int> originalFileIdToNewFileId;
237
238 int filesCount = 0;
239 m_query->exec(
240 "SELECT "
241 "FileNameTable.FileId, "
242 "FileNameTable.Name, "
243 "FileNameTable.Title "
244 "FROM FileNameTable, FolderTable "
245 "WHERE FileNameTable.FolderId = FolderTable.Id "
246 "ORDER BY FileId"_L1);
247 while (m_query->next()) {
248 const int fileId = m_query->value(0).toInt();
249 FileItem fileItem;
250 fileItem.name = m_query->value(1).toString();
251 fileItem.title = m_query->value(2).toString();
252
253 idToFileItem.insert(fileId, fileItem);
254 originalFileIdToNewFileId.insert(fileId, filesCount);
255 ++filesCount;
256 }
257
258 QMap<int, ContentsItem> idToContentsItem;
259
260 m_query->exec("SELECT Data, Id FROM ContentsTable ORDER BY Id"_L1);
261 while (m_query->next()) {
262 ContentsItem contentsItem;
263 contentsItem.data = m_query->value(0).toByteArray();
264 const int contentsId = m_query->value(1).toInt();
265
266 idToContentsItem.insert(contentsId, contentsItem);
267 }
268
269 bool optimized = true;
270
271 if (usedAttributeCount) {
272 // May optimize only when all usedAttributes are attached to every
273 // index and file. It means the number of rows in the
274 // IndexTable multiplied by number of used attributes
275 // must equal the number of rows inside IndexFilterTable
276 // (yes, we have a combinatorial explosion of data in IndexFilterTable,
277 // which we want to optimize). The same with FileNameTable and
278 // FileFilterTable.
279
280 const bool mayOptimizeIndexTable = filterDataCount(m_query.get(), "IndexFilterTable"_L1)
281 == idToIndexItem.size() * usedAttributeCount;
282 const bool mayOptimizeFileTable = filterDataCount(m_query.get(), "FileFilterTable"_L1)
283 == idToFileItem.size() * usedAttributeCount;
284 const bool mayOptimizeContentsTable =
285 filterDataCount(m_query.get(), "ContentsFilterTable"_L1)
286 == idToContentsItem.size() * usedAttributeCount;
287 optimized = mayOptimizeIndexTable && mayOptimizeFileTable && mayOptimizeContentsTable;
288
289 if (!optimized) {
290 m_query->exec(
291 "SELECT "
292 "IndexFilterTable.IndexId, "
293 "FilterAttributeTable.Name "
294 "FROM "
295 "IndexFilterTable, "
296 "FilterAttributeTable "
297 "WHERE "
298 "IndexFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
299 while (m_query->next()) {
300 const int indexId = m_query->value(0).toInt();
301 auto it = idToIndexItem.find(indexId);
302 if (it != idToIndexItem.end())
303 it.value().filterAttributes.append(m_query->value(1).toString());
304 }
305
306 m_query->exec(
307 "SELECT "
308 "FileFilterTable.FileId, "
309 "FilterAttributeTable.Name "
310 "FROM "
311 "FileFilterTable, "
312 "FilterAttributeTable "
313 "WHERE "
314 "FileFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
315 while (m_query->next()) {
316 const int fileId = m_query->value(0).toInt();
317 auto it = idToFileItem.find(fileId);
318 if (it != idToFileItem.end())
319 it.value().filterAttributes.append(m_query->value(1).toString());
320 }
321
322 m_query->exec(
323 "SELECT "
324 "ContentsFilterTable.ContentsId, "
325 "FilterAttributeTable.Name "
326 "FROM "
327 "ContentsFilterTable, "
328 "FilterAttributeTable "
329 "WHERE "
330 "ContentsFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
331 while (m_query->next()) {
332 const int contentsId = m_query->value(0).toInt();
333 auto it = idToContentsItem.find(contentsId);
334 if (it != idToContentsItem.end())
335 it.value().filterAttributes.append(m_query->value(1).toString());
336 }
337 }
338 }
339
340 // reindex fileId references
341 for (auto it = idToIndexItem.cbegin(), end = idToIndexItem.cend(); it != end; ++it) {
342 IndexItem item = it.value();
343 item.fileId = originalFileIdToNewFileId.value(item.fileId);
344 table.indexItems.append(item);
345 }
346
347 table.fileItems = idToFileItem.values();
348 table.contentsItems = idToContentsItem.values();
349
350 if (optimized) {
351 for (int attributeId : std::as_const(usedAttributeIds))
352 table.usedFilterAttributes.append(attributeIds.value(attributeId));
353 }
354 return table;
355}
356
358{
359 QList<QStringList> result;
360 if (m_query) {
361 m_query->exec(
362 "SELECT "
363 "FileAttributeSetTable.Id, "
364 "FilterAttributeTable.Name "
365 "FROM "
366 "FileAttributeSetTable, "
367 "FilterAttributeTable "
368 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
369 "ORDER BY FileAttributeSetTable.Id"_L1);
370 int oldId = -1;
371 while (m_query->next()) {
372 const int id = m_query->value(0).toInt();
373 if (id != oldId) {
374 result.append(QStringList());
375 oldId = id;
376 }
377 result.last().append(m_query->value(1).toString());
378 }
379 }
380 return result;
381}
382
383QByteArray QHelpDBReader::fileData(const QString &virtualFolder,
384 const QString &filePath) const
385{
386 QByteArray ba;
387 if (virtualFolder.isEmpty() || filePath.isEmpty() || !m_query)
388 return ba;
389
390 namespaceName();
391 m_query->prepare(
392 "SELECT "
393 "FileDataTable.Data "
394 "FROM "
395 "FileDataTable, "
396 "FileNameTable, "
397 "FolderTable, "
398 "NamespaceTable "
399 "WHERE FileDataTable.Id = FileNameTable.FileId "
400 "AND (FileNameTable.Name = ? OR FileNameTable.Name = ?) "
401 "AND FileNameTable.FolderId = FolderTable.Id "
402 "AND FolderTable.Name = ? "
403 "AND FolderTable.NamespaceId = NamespaceTable.Id "
404 "AND NamespaceTable.Name = ?"_L1);
405 m_query->bindValue(0, filePath);
406 m_query->bindValue(1, QString("./"_L1 + filePath));
407 m_query->bindValue(2, virtualFolder);
408 m_query->bindValue(3, m_namespace);
409 m_query->exec();
410 if (m_query->next() && m_query->isValid())
411 ba = qUncompress(m_query->value(0).toByteArray());
412 return ba;
413}
414
416{
417 QStringList lst;
418 if (m_query) {
419 m_query->exec("SELECT Name FROM FilterNameTable"_L1);
420 while (m_query->next())
421 lst.append(m_query->value(0).toString());
422 }
423 return lst;
424}
425
426QStringList QHelpDBReader::filterAttributes(const QString &filterName) const
427{
428 QStringList lst;
429 if (m_query) {
430 if (filterName.isEmpty()) {
431 m_query->prepare("SELECT Name FROM FilterAttributeTable"_L1);
432 } else {
433 m_query->prepare(
434 "SELECT "
435 "FilterAttributeTable.Name "
436 "FROM "
437 "FilterAttributeTable, "
438 "FilterTable, "
439 "FilterNameTable "
440 "WHERE FilterNameTable.Name = ? "
441 "AND FilterNameTable.Id = FilterTable.NameId "
442 "AND FilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
443 m_query->bindValue(0, filterName);
444 }
445 m_query->exec();
446 while (m_query->next())
447 lst.append(m_query->value(0).toString());
448 }
449 return lst;
450}
451
452QMultiMap<QString, QByteArray> QHelpDBReader::filesData(const QStringList &filterAttributes,
453 const QString &extensionFilter) const
454{
455 if (!m_query)
456 return {};
457
458 QString query;
459 QString extension;
460 if (!extensionFilter.isEmpty())
461 extension = "AND FileNameTable.Name LIKE \'%.%1\'"_L1.arg(extensionFilter);
462
463 if (filterAttributes.isEmpty()) {
464 query =
465 "SELECT "
466 "FileNameTable.Name, "
467 "FileDataTable.Data "
468 "FROM "
469 "FolderTable, "
470 "FileNameTable, "
471 "FileDataTable "
472 "WHERE FileDataTable.Id = FileNameTable.FileId "
473 "AND FileNameTable.FolderId = FolderTable.Id %1"_L1.arg(extension);
474 } else {
475 for (int i = 0; i < filterAttributes.size(); ++i) {
476 if (i > 0)
477 query.append(" INTERSECT "_L1);
478 query.append(
479 "SELECT "
480 "FileNameTable.Name, "
481 "FileDataTable.Data "
482 "FROM "
483 "FolderTable, "
484 "FileNameTable, "
485 "FileDataTable, "
486 "FileFilterTable, "
487 "FilterAttributeTable "
488 "WHERE FileDataTable.Id = FileNameTable.FileId "
489 "AND FileNameTable.FolderId = FolderTable.Id "
490 "AND FileNameTable.FileId = FileFilterTable.FileId "
491 "AND FileFilterTable.FilterAttributeId = FilterAttributeTable.Id "
492 "AND FilterAttributeTable.Name = \'%1\' %2"_L1
493 .arg(quote(filterAttributes.at(i)), extension));
494 }
495 }
496 m_query->exec(query);
497 QMultiMap<QString, QByteArray> result;
498 while (m_query->next())
499 result.insert(m_query->value(0).toString(), qUncompress(m_query->value(1).toByteArray()));
500 return result;
501}
502
503QVariant QHelpDBReader::metaData(const QString &name) const
504{
505 if (!m_query)
506 return {};
507
508 m_query->prepare("SELECT COUNT(Value), Value FROM MetaDataTable WHERE Name=?"_L1);
509 m_query->bindValue(0, name);
510 if (m_query->exec() && m_query->next() && m_query->value(0).toInt() == 1)
511 return m_query->value(1);
512 return {};
513}
514
515QString QHelpDBReader::quote(const QString &string) const
516{
517 QString s = string;
518 s.replace(u'\'', "\'\'"_L1);
519 return s;
520}
521
522QT_END_NAMESPACE
QString namespaceName() const
IndexTable indexTable() const
QString virtualFolder() const
QString version() const
QList< QStringList > filterAttributeSets() const
QStringList customFilters() const
\inmodule QtCore
Combined button and popup list for selecting options.
static bool isAttributeUsed(QSqlQuery *query, const QString &tableName, int attributeId)
static VersionData getVersionData(const QString &nameSpace)
static int filterDataCount(QSqlQuery *query, const QString &tableName)