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

source code of qttools/src/assistant/help/qhelpdbreader.cpp