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