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:critical reason:data-parser
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 // Don't let the queries cache the already fetched rows, as all the data is copied
196 // into the table anyway.
197 m_query->setForwardOnly(true);
198
199 QMap<int, QString> attributeIds;
200 m_query->exec("SELECT DISTINCT Id, Name FROM FilterAttributeTable ORDER BY Id"_L1);
201 while (m_query->next())
202 attributeIds.insert(m_query->value(0).toInt(), m_query->value(1).toString());
203
204 // Maybe some are unused and specified erroneously in the named filter only,
205 // like it was in case of qtlocation.qch <= qt 5.9
206 QList<int> usedAttributeIds;
207 for (auto it = attributeIds.cbegin(), end = attributeIds.cend(); it != end; ++it) {
208 const int attributeId = it.key();
209 if (isAttributeUsed(m_query.get(), "IndexFilterTable"_L1, attributeId)
210 || isAttributeUsed(m_query.get(), "ContentsFilterTable"_L1, attributeId)
211 || isAttributeUsed(m_query.get(), "FileFilterTable"_L1, attributeId)) {
212 usedAttributeIds.append(attributeId);
213 }
214 }
215
216 bool legacy = false;
217 m_query->exec("SELECT * FROM pragma_table_info('IndexTable') WHERE name='ContextName'"_L1);
218 if (m_query->next())
219 legacy = true;
220
221 const QString identifierColumnName = legacy ? "ContextName"_L1 : "Identifier"_L1;
222 const int usedAttributeCount = usedAttributeIds.size();
223
224 QMap<int, IndexItem> idToIndexItem;
225 m_query->exec(QString::fromLatin1("SELECT Name, %1, FileId, Anchor, Id "
226 "FROM IndexTable "
227 "ORDER BY Id").arg(identifierColumnName));
228 while (m_query->next()) {
229 IndexItem indexItem;
230 indexItem.name = m_query->value(0).toString();
231 indexItem.identifier = m_query->value(1).toString();
232 indexItem.fileId = m_query->value(2).toInt();
233 indexItem.anchor = m_query->value(3).toString();
234 const int indexId = m_query->value(4).toInt();
235
236 idToIndexItem.insert(indexId, indexItem);
237 }
238
239 QMap<int, FileItem> idToFileItem;
240 QMap<int, int> originalFileIdToNewFileId;
241
242 int filesCount = 0;
243 m_query->exec(
244 "SELECT "
245 "FileNameTable.FileId, "
246 "FileNameTable.Name, "
247 "FileNameTable.Title "
248 "FROM FileNameTable, FolderTable "
249 "WHERE FileNameTable.FolderId = FolderTable.Id "
250 "ORDER BY FileId"_L1);
251 while (m_query->next()) {
252 const int fileId = m_query->value(0).toInt();
253 FileItem fileItem;
254 fileItem.name = m_query->value(1).toString();
255 fileItem.title = m_query->value(2).toString();
256
257 idToFileItem.insert(fileId, fileItem);
258 originalFileIdToNewFileId.insert(fileId, filesCount);
259 ++filesCount;
260 }
261
262 QMap<int, ContentsItem> idToContentsItem;
263
264 m_query->exec("SELECT Data, Id FROM ContentsTable ORDER BY Id"_L1);
265 while (m_query->next()) {
266 ContentsItem contentsItem;
267 contentsItem.data = m_query->value(0).toByteArray();
268 const int contentsId = m_query->value(1).toInt();
269
270 idToContentsItem.insert(contentsId, contentsItem);
271 }
272
273 bool optimized = true;
274
275 if (usedAttributeCount) {
276 // May optimize only when all usedAttributes are attached to every
277 // index and file. It means the number of rows in the
278 // IndexTable multiplied by number of used attributes
279 // must equal the number of rows inside IndexFilterTable
280 // (yes, we have a combinatorial explosion of data in IndexFilterTable,
281 // which we want to optimize). The same with FileNameTable and
282 // FileFilterTable.
283
284 const bool mayOptimizeIndexTable = filterDataCount(m_query.get(), "IndexFilterTable"_L1)
285 == idToIndexItem.size() * usedAttributeCount;
286 const bool mayOptimizeFileTable = filterDataCount(m_query.get(), "FileFilterTable"_L1)
287 == idToFileItem.size() * usedAttributeCount;
288 const bool mayOptimizeContentsTable =
289 filterDataCount(m_query.get(), "ContentsFilterTable"_L1)
290 == idToContentsItem.size() * usedAttributeCount;
291 optimized = mayOptimizeIndexTable && mayOptimizeFileTable && mayOptimizeContentsTable;
292
293 if (!optimized) {
294 m_query->exec(
295 "SELECT "
296 "IndexFilterTable.IndexId, "
297 "FilterAttributeTable.Name "
298 "FROM "
299 "IndexFilterTable, "
300 "FilterAttributeTable "
301 "WHERE "
302 "IndexFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
303 while (m_query->next()) {
304 const int indexId = m_query->value(0).toInt();
305 auto it = idToIndexItem.find(indexId);
306 if (it != idToIndexItem.end())
307 it.value().filterAttributes.append(m_query->value(1).toString());
308 }
309
310 m_query->exec(
311 "SELECT "
312 "FileFilterTable.FileId, "
313 "FilterAttributeTable.Name "
314 "FROM "
315 "FileFilterTable, "
316 "FilterAttributeTable "
317 "WHERE "
318 "FileFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
319 while (m_query->next()) {
320 const int fileId = m_query->value(0).toInt();
321 auto it = idToFileItem.find(fileId);
322 if (it != idToFileItem.end())
323 it.value().filterAttributes.append(m_query->value(1).toString());
324 }
325
326 m_query->exec(
327 "SELECT "
328 "ContentsFilterTable.ContentsId, "
329 "FilterAttributeTable.Name "
330 "FROM "
331 "ContentsFilterTable, "
332 "FilterAttributeTable "
333 "WHERE "
334 "ContentsFilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
335 while (m_query->next()) {
336 const int contentsId = m_query->value(0).toInt();
337 auto it = idToContentsItem.find(contentsId);
338 if (it != idToContentsItem.end())
339 it.value().filterAttributes.append(m_query->value(1).toString());
340 }
341 }
342 }
343
344 // reindex fileId references
345 for (auto it = idToIndexItem.cbegin(), end = idToIndexItem.cend(); it != end; ++it) {
346 IndexItem item = it.value();
347 // An unknown file id is kept invalid, it's rejected when registering the index.
348 item.fileId = originalFileIdToNewFileId.value(item.fileId, -1);
349 table.indexItems.append(item);
350 }
351
352 table.fileItems = idToFileItem.values();
353 table.contentsItems = idToContentsItem.values();
354
355 if (optimized) {
356 for (int attributeId : std::as_const(usedAttributeIds))
357 table.usedFilterAttributes.append(attributeIds.value(attributeId));
358 }
359 m_query->finish();
360 m_query->setForwardOnly(false);
361 return table;
362}
363
365{
366 QList<QStringList> result;
367 if (m_query) {
368 m_query->exec(
369 "SELECT "
370 "FileAttributeSetTable.Id, "
371 "FilterAttributeTable.Name "
372 "FROM "
373 "FileAttributeSetTable, "
374 "FilterAttributeTable "
375 "WHERE FileAttributeSetTable.FilterAttributeId = FilterAttributeTable.Id "
376 "ORDER BY FileAttributeSetTable.Id"_L1);
377 int oldId = -1;
378 while (m_query->next()) {
379 const int id = m_query->value(0).toInt();
380 if (id != oldId) {
381 result.append(QStringList());
382 oldId = id;
383 }
384 result.last().append(m_query->value(1).toString());
385 }
386 }
387 return result;
388}
389
390QByteArray QHelpDBReader::fileData(const QString &virtualFolder,
391 const QString &filePath) const
392{
393 QByteArray ba;
394 if (virtualFolder.isEmpty() || filePath.isEmpty() || !m_query)
395 return ba;
396
397 namespaceName();
398 m_query->prepare(
399 "SELECT "
400 "FileDataTable.Data "
401 "FROM "
402 "FileDataTable, "
403 "FileNameTable, "
404 "FolderTable, "
405 "NamespaceTable "
406 "WHERE FileDataTable.Id = FileNameTable.FileId "
407 "AND (FileNameTable.Name = ? OR FileNameTable.Name = ?) "
408 "AND FileNameTable.FolderId = FolderTable.Id "
409 "AND FolderTable.Name = ? "
410 "AND FolderTable.NamespaceId = NamespaceTable.Id "
411 "AND NamespaceTable.Name = ?"_L1);
412 m_query->bindValue(0, filePath);
413 m_query->bindValue(1, QString("./"_L1 + filePath));
414 m_query->bindValue(2, virtualFolder);
415 m_query->bindValue(3, m_namespace);
416 m_query->exec();
417 if (m_query->next() && m_query->isValid())
418 ba = qUncompress(m_query->value(0).toByteArray());
419 return ba;
420}
421
423{
424 QStringList lst;
425 if (m_query) {
426 m_query->exec("SELECT Name FROM FilterNameTable"_L1);
427 while (m_query->next())
428 lst.append(m_query->value(0).toString());
429 }
430 return lst;
431}
432
433QStringList QHelpDBReader::filterAttributes(const QString &filterName) const
434{
435 QStringList lst;
436 if (m_query) {
437 if (filterName.isEmpty()) {
438 m_query->prepare("SELECT Name FROM FilterAttributeTable"_L1);
439 } else {
440 m_query->prepare(
441 "SELECT "
442 "FilterAttributeTable.Name "
443 "FROM "
444 "FilterAttributeTable, "
445 "FilterTable, "
446 "FilterNameTable "
447 "WHERE FilterNameTable.Name = ? "
448 "AND FilterNameTable.Id = FilterTable.NameId "
449 "AND FilterTable.FilterAttributeId = FilterAttributeTable.Id"_L1);
450 m_query->bindValue(0, filterName);
451 }
452 m_query->exec();
453 while (m_query->next())
454 lst.append(m_query->value(0).toString());
455 }
456 return lst;
457}
458
459void QHelpDBReader::filesData(const QStringList &filterAttributes,
460 const QStringList &extensionFilters,
461 const std::function<bool(const QString &fileName,
462 const QByteArray &data)> &handler) const
463{
464 if (!m_query)
465 return;
466
467 QString query;
468 QString extension;
469 if (!extensionFilters.isEmpty()) {
470 QStringList conditions;
471 for (const QString &extensionFilter : extensionFilters)
472 conditions.append("FileNameTable.Name LIKE \'%.%1\'"_L1.arg(extensionFilter));
473 extension = "AND (%1)"_L1.arg(conditions.join(" OR "_L1));
474 }
475
476 if (filterAttributes.isEmpty()) {
477 query =
478 "SELECT "
479 "FileNameTable.Name, "
480 "FileDataTable.Data "
481 "FROM "
482 "FolderTable, "
483 "FileNameTable, "
484 "FileDataTable "
485 "WHERE FileDataTable.Id = FileNameTable.FileId "
486 "AND FileNameTable.FolderId = FolderTable.Id %1"_L1.arg(extension);
487 } else {
488 for (int i = 0; i < filterAttributes.size(); ++i) {
489 if (i > 0)
490 query.append(" INTERSECT "_L1);
491 query.append(
492 "SELECT "
493 "FileNameTable.Name, "
494 "FileDataTable.Data "
495 "FROM "
496 "FolderTable, "
497 "FileNameTable, "
498 "FileDataTable, "
499 "FileFilterTable, "
500 "FilterAttributeTable "
501 "WHERE FileDataTable.Id = FileNameTable.FileId "
502 "AND FileNameTable.FolderId = FolderTable.Id "
503 "AND FileNameTable.FileId = FileFilterTable.FileId "
504 "AND FileFilterTable.FilterAttributeId = FilterAttributeTable.Id "
505 "AND FilterAttributeTable.Name = \'%1\' %2"_L1
506 .arg(quote(filterAttributes.at(i)), extension));
507 }
508 }
509 // Don't let the query cache the already fetched rows, so that only one file is held
510 // in memory at a time.
511 m_query->setForwardOnly(true);
512 m_query->exec(query);
513 while (m_query->next()) {
514 if (!handler(m_query->value(0).toString(),
515 qUncompress(m_query->value(1).toByteArray()))) {
516 break;
517 }
518 }
519 m_query->finish();
520 m_query->setForwardOnly(false);
521}
522
523QVariant QHelpDBReader::metaData(const QString &name) const
524{
525 if (!m_query)
526 return {};
527
528 m_query->prepare("SELECT COUNT(Value), Value FROM MetaDataTable WHERE Name=?"_L1);
529 m_query->bindValue(0, name);
530 if (m_query->exec() && m_query->next() && m_query->value(0).toInt() == 1)
531 return m_query->value(1);
532 return {};
533}
534
535QString QHelpDBReader::quote(const QString &string) const
536{
537 QString s = string;
538 s.replace(u'\'', "\'\'"_L1);
539 return s;
540}
541
542QT_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)