KDb

KDb.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2004-2018 Jarosław Staniek <staniek@kde.org>
3 Copyright (c) 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
4 Copyright (c) 1999 Preston Brown <pbrown@kde.org>
5 Copyright (c) 1997 Matthias Kalle Dalheimer <kalle@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21*/
22
23#include "KDb.h"
24#include "KDbConnection.h"
25#include "KDbConnectionData.h"
26#include "KDbCursor.h"
27#include "KDbDateTime.h"
28#include "KDbDriverBehavior.h"
29#include "KDbDriverManager.h"
30#include "KDbDriver_p.h"
31#include "KDbLookupFieldSchema.h"
32#include "KDbMessageHandler.h"
33#include "KDbNativeStatementBuilder.h"
34#include "KDbQuerySchema.h"
35#include "KDbRecordData.h"
36#include "KDbSqlResult.h"
37#include "KDbTableOrQuerySchema.h"
38#include "KDbVersionInfo.h"
39#include "KDb_p.h"
40#include "kdb_debug.h"
41#include "transliteration/transliteration_table.h"
42
43#include <QMap>
44#include <QHash>
45#include <QBuffer>
46#include <QPixmap>
47#include <QSet>
48#include <QTimer>
49#include <QThread>
50#include <QProgressDialog>
51#include <QDomNode>
52#include <QApplication>
53#include <QDir>
54#include <QProcess>
55#include <QtDebug>
56
57#include <limits>
58#include <memory>
59
60Q_DECLARE_METATYPE(KDbField::Type)
61
62class ConnectionTestDialog;
63
64class ConnectionTestThread : public QThread
65{
67public:
68 ConnectionTestThread(ConnectionTestDialog *dlg, const KDbConnectionData& connData);
69 void run() override;
71 void error(const QString& msg, const QString& details);
72protected:
73 void emitError(const KDbResultable& KDbResultable);
74
75 ConnectionTestDialog* m_dlg;
76 KDbConnectionData m_connData;
77 KDbDriver *m_driver;
78private:
79 Q_DISABLE_COPY(ConnectionTestThread)
80};
81
82class ConnectionTestDialog : public QProgressDialog // krazy:exclude=qclasses
83{
85public:
86 ConnectionTestDialog(const KDbConnectionData& data, KDbMessageHandler* msgHandler,
87 QWidget* parent = nullptr);
88 ~ConnectionTestDialog() override;
89
90 int exec() override;
91
92public Q_SLOTS:
93 void error(const QString& msg, const QString& details);
94
95protected Q_SLOTS:
96 void slotTimeout();
97 void accept() override;
98 void reject() override;
99
100protected:
101 void finish();
102
104 KDbConnectionData m_connData;
105 QTimer m_timer;
106 KDbMessageHandler* m_msgHandler;
107 int m_elapsedTime;
108 bool m_error;
109 QString m_msg;
110 QString m_details;
111 bool m_stopWaiting;
112
113private:
114 Q_DISABLE_COPY(ConnectionTestDialog)
115};
116
117ConnectionTestThread::ConnectionTestThread(ConnectionTestDialog* dlg, const KDbConnectionData& connData)
118 : m_dlg(dlg), m_connData(connData)
119{
120 connect(this, SIGNAL(error(QString,QString)),
122
123 // try to load driver now because it's not supported in different thread
124 KDbDriverManager manager;
125 m_driver = manager.driver(m_connData.driverId());
126 if (manager.result().isError()) {
127 emitError(*manager.resultable());
128 m_driver = nullptr;
129 }
130}
131
132void ConnectionTestThread::emitError(const KDbResultable& KDbResultable)
133{
134 QString msg;
135 QString details;
136 KDb::getHTMLErrorMesage(KDbResultable, &msg, &details);
137 emit error(msg, details);
138}
139
140void ConnectionTestThread::run()
141{
142 if (!m_driver) {
143 return;
144 }
145 QScopedPointer<KDbConnection> conn(m_driver->createConnection(m_connData));
146 if (conn.isNull() || m_driver->result().isError()) {
147 emitError(*m_driver);
148 return;
149 }
150 if (!conn->connect() || conn->result().isError()) {
151 emitError(*conn);
152 return;
153 }
154 // SQL database backends like PostgreSQL require executing "USE database"
155 // if we really want to know connection to the server succeeded.
156 QString tmpDbName;
157 if (!conn->useTemporaryDatabaseIfNeeded(&tmpDbName)) {
158 emitError(*conn);
159 return;
160 }
161 if (!tmpDbName.isEmpty()) {
162 if (!conn->closeDatabase()) {
163 emitError(*conn);
164 }
165 }
166 emitError(KDbResultable());
167}
168
169ConnectionTestDialog::ConnectionTestDialog(const KDbConnectionData& data,
170 KDbMessageHandler* msgHandler, QWidget* parent)
171 : QProgressDialog(parent)
172 , m_thread(new ConnectionTestThread(this, data))
173 , m_connData(data)
174 , m_msgHandler(msgHandler)
175 , m_elapsedTime(0)
176 , m_error(false)
177 , m_stopWaiting(false)
178{
179 setWindowTitle(tr("Test Connection", "Dialog's title: testing database connection"));
180 setLabelText(tr("Testing connection to \"%1\" database server...")
181 .arg(data.toUserVisibleString()));
182 setModal(true);
183 setRange(0, 0); //to show busy indicator
184 connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
185 adjustSize();
186 resize(250, height());
187}
188
189ConnectionTestDialog::~ConnectionTestDialog()
190{
191 if (m_thread->isRunning()) {
192 m_thread->terminate();
193 }
194 m_thread->deleteLater();
195}
196
197int ConnectionTestDialog::exec()
198{
199 //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread();
200 m_timer.start(20);
201 m_thread->start();
202 const int res = QProgressDialog::exec(); // krazy:exclude=qclasses
203 m_thread->wait();
204 m_timer.stop();
205 return res;
206}
207
208void ConnectionTestDialog::slotTimeout()
209{
210 //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread();
211 //kdbDebug() << m_error;
212 bool notResponding = false;
213 if (m_elapsedTime >= 1000*5) {//5 seconds
214 m_stopWaiting = true;
215 notResponding = true;
216 }
217 //kdbDebug() << m_elapsedTime << m_stopWaiting << notResponding;
218 if (m_stopWaiting) {
219 m_timer.disconnect(this);
220 m_timer.stop();
221 QString message;
222 QString details;
224 if (m_error) {
225 reject();
226 //kdbDebug() << "after reject";
227 message = tr("Test connection to \"%1\" database server failed.")
228 .arg(m_connData.toUserVisibleString());
229 details = m_msg;
230 if (!m_details.isEmpty()) {
231 details += QLatin1Char('\n') + m_details;
232 }
233 type = KDbMessageHandler::Sorry;
234 m_error = false;
235 } else if (notResponding) {
236 reject();
237 //kdbDebug() << "after reject";
238 message = tr("Test connection to \"%1\" database server failed. The server is not responding.")
239 .arg(m_connData.toUserVisibleString());
240 type = KDbMessageHandler::Sorry;
241 } else {
242 accept();
243 //kdbDebug() << "after accept";
244 message = tr("Test connection to \"%1\" database server established successfully.")
245 .arg(m_connData.toUserVisibleString()),
246 type = KDbMessageHandler::Information;
247 }
248 if (m_msgHandler) {
249 m_msgHandler->showErrorMessage(type, message, details, tr("Test Connection"));
250 }
251 return;
252 }
253 m_elapsedTime += 20;
254 setValue(m_elapsedTime);
255}
256
257void ConnectionTestDialog::error(const QString& msg, const QString& details)
258{
259 //kdbDebug() << "tid:" << QThread::currentThread() << "this_thread:" << thread();
260 //kdbDebug() << msg << details;
261 m_stopWaiting = true;
262 m_msg = msg;
263 m_details = details;
264 m_error = !msg.isEmpty() || !details.isEmpty();
265 if (m_error) {
266 kdbDebug() << "Error:" << msg << details;
267 }
268}
269
270void ConnectionTestDialog::accept()
271{
272 finish();
273 QProgressDialog::accept(); // krazy:exclude=qclasses
274}
275
276void ConnectionTestDialog::reject()
277{
278 finish();
279 QProgressDialog::reject(); // krazy:exclude=qclasses
280}
281
282void ConnectionTestDialog::finish()
283{
284 if (m_thread->isRunning()) {
285 m_thread->terminate();
286 }
287 m_timer.disconnect(this);
288 m_timer.stop();
289}
290
291// ----
292
293//! @return hex digit converted to integer (0 to 15), 0xFF on failure
294inline static unsigned char hexDigitToInt(char digit)
295{
296 if (digit >= '0' && digit <= '9') {
297 return digit - '0';
298 }
299 if (digit >= 'a' && digit <= 'f') {
300 return digit - 'a' + 10;
301 }
302 if (digit >= 'A' && digit <= 'F') {
303 return digit - 'A' + 10;
304 }
305 return 0xFF;
306}
307
308//! Converts textual representation @a data of a hex number (@a length digits) to a byte array @a array
309//! @return true on success and false if @a data contains characters that are not hex digits.
310//! true is returned for empty @a data as well.
311inline static bool hexToByteArrayInternal(const char* data, int length, QByteArray *array)
312{
313 Q_ASSERT(length >= 0);
314 Q_ASSERT(data || length == 0);
315 array->resize(length / 2 + length % 2);
316 for(int i = 0; length > 0; --length, ++data, ++i) {
317 unsigned char d1 = hexDigitToInt(data[0]);
318 unsigned char d2;
319 if (i == 0 && (length % 2) == 1) { // odd number of digits; no leading 0
320 d2 = d1;
321 d1 = 0;
322 }
323 else {
324 --length;
325 ++data;
326 d2 = hexDigitToInt(data[0]);
327 }
328 if (d1 == 0xFF || d2 == 0xFF) {
329 return false;
330 }
331 (*array)[i] = (d1 << 4) + d2;
332 }
333 return true;
334}
335
337{
338 return KDbVersionInfo(
339 KDB_VERSION_MAJOR, KDB_VERSION_MINOR, KDB_VERSION_PATCH);
340}
341
342bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName,
343 const QString &keyname, KDbField::Type keytype, const QVariant &keyval)
344{
345 return conn
346 ? conn->executeSql(KDbEscapedString("DELETE FROM %1 WHERE %2=%3")
347 .arg(conn->escapeIdentifier(tableName))
348 .arg(conn->escapeIdentifier(keyname))
349 .arg(conn->driver()->valueToSql(keytype, keyval)))
350 : false;
351}
352
353bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName,
354 const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1,
355 const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2)
356{
357 return conn
358 ? conn->executeSql(KDbEscapedString("DELETE FROM %1 WHERE %2=%3 AND %4=%5")
359 .arg(conn->escapeIdentifier(tableName))
360 .arg(conn->escapeIdentifier(keyname1))
361 .arg(conn->driver()->valueToSql(keytype1, keyval1))
362 .arg(conn->escapeIdentifier(keyname2))
363 .arg(conn->driver()->valueToSql(keytype2, keyval2)))
364 : false;
365}
366
367bool KDb::deleteRecords(KDbConnection* conn, const QString &tableName,
368 const QString &keyname1, KDbField::Type keytype1, const QVariant& keyval1,
369 const QString &keyname2, KDbField::Type keytype2, const QVariant& keyval2,
370 const QString &keyname3, KDbField::Type keytype3, const QVariant& keyval3)
371{
372 return conn
373 ? conn->executeSql(KDbEscapedString("DELETE FROM %1 WHERE %2=%3 AND %4=%5 AND %6=%7")
374 .arg(conn->escapeIdentifier(tableName))
375 .arg(conn->escapeIdentifier(keyname1))
376 .arg(conn->driver()->valueToSql(keytype1, keyval1))
377 .arg(conn->escapeIdentifier(keyname2))
378 .arg(conn->driver()->valueToSql(keytype2, keyval2))
379 .arg(conn->escapeIdentifier(keyname3))
380 .arg(conn->driver()->valueToSql(keytype3, keyval3)))
381 : false;
382}
383
384bool KDb::deleteAllRecords(KDbConnection* conn, const QString &tableName)
385{
386 return conn
387 ? conn->executeSql(
388 KDbEscapedString("DELETE FROM %1").arg(conn->escapeIdentifier(tableName)))
389 : false;
390}
391
393 const QString &autoIncrementFieldName,
394 const QString &tableName, quint64 *recordId)
395{
396 if (!result) {
397 return std::numeric_limits<quint64>::max();
398 }
399 const quint64 foundRecordId = result->lastInsertRecordId();
400 if (recordId) {
401 *recordId = foundRecordId;
402 }
403 return KDb::lastInsertedAutoIncValue(result->connection(),
404 foundRecordId, autoIncrementFieldName, tableName);
405}
406
407KDB_EXPORT quint64 KDb::lastInsertedAutoIncValue(KDbConnection *conn, const quint64 recordId,
408 const QString &autoIncrementFieldName,
409 const QString &tableName)
410{
411 const KDbDriverBehavior *behavior = KDbDriverPrivate::behavior(conn->driver());
413 return recordId;
414 }
415 KDbRecordData rdata;
416 if (recordId == std::numeric_limits<quint64>::max()
417 || true != conn->querySingleRecord(
418 KDbEscapedString("SELECT ") + escapeIdentifier(tableName) + '.'
419 + escapeIdentifier(autoIncrementFieldName)
420 + " FROM " + escapeIdentifier(tableName)
421 + " WHERE " + behavior->ROW_ID_FIELD_NAME
422 + '=' + KDbEscapedString::number(recordId), &rdata))
423 {
424 return std::numeric_limits<quint64>::max();
425 }
426 return rdata[0].toULongLong();
427}
428
430{
431 if (KDbField::isTextType(type)) {
432 return value.toString().isEmpty() && !value.toString().isNull();
433 }
434 else if (type == KDbField::BLOB) {
435 return value.toByteArray().isEmpty() && !value.toByteArray().isNull();
436 }
437 return value.isNull();
438}
439
441 const QString& fieldName, const QVariant& value)
442{
443 if (value.isNull())
444 return KDbEscapedString(fieldName) + " IS NULL";
445 return KDbEscapedString(fieldName) + '=' + drv->valueToSql(t, value);
446}
447
448//! Cache
449struct TypeCache {
450 TypeCache() {
454 QStringList name_list, str_list;
455 if (tlist.contains(tg)) {
456 list = tlist.value(tg);
457 name_list = nlist.value(tg);
458 str_list = slist.value(tg);
459 }
460 list += t;
461 name_list += KDbField::typeName(t);
462 str_list += KDbField::typeString(t);
463 tlist[ tg ] = list;
464 nlist[ tg ] = name_list;
465 slist[ tg ] = str_list;
466 }
467
468 def_tlist[ KDbField::InvalidGroup ] = KDbField::InvalidType;
469 def_tlist[ KDbField::TextGroup ] = KDbField::Text;
470 def_tlist[ KDbField::IntegerGroup ] = KDbField::Integer;
471 def_tlist[ KDbField::FloatGroup ] = KDbField::Double;
472 def_tlist[ KDbField::BooleanGroup ] = KDbField::Boolean;
473 def_tlist[ KDbField::DateTimeGroup ] = KDbField::Date;
474 def_tlist[ KDbField::BLOBGroup ] = KDbField::BLOB;
475 }
476
481};
482
483Q_GLOBAL_STATIC(TypeCache, KDb_typeCache)
484
485const QList<KDbField::Type> KDb::fieldTypesForGroup(KDbField::TypeGroup typeGroup)
486{
487 return KDb_typeCache->tlist.value(typeGroup);
488}
489
491{
492 return KDb_typeCache->nlist.value(typeGroup);
493}
494
496{
497 return KDb_typeCache->slist.value(typeGroup);
498}
499
501{
502 return (typeGroup <= KDbField::LastTypeGroup) ? KDb_typeCache->def_tlist.value(typeGroup) : KDbField::InvalidType;
503}
504
505void KDb::getHTMLErrorMesage(const KDbResultable& resultable, QString *msg, QString *details)
506{
507 if (!msg) {
508 kdbWarning() << "Missing 'msg' parameter";
509 return;
510 }
511 if (!details) {
512 kdbWarning() << "Missing 'details' parameter";
513 return;
514 }
515 const KDbResult result(resultable.result());
516 if (!result.isError())
517 return;
518 //lower level message is added to the details, if there is alread message specified
519 if (!result.messageTitle().isEmpty())
520 *msg += QLatin1String("<p>") + result.messageTitle();
521
522 if (msg->isEmpty())
523 *msg = QLatin1String("<p>") + result.message();
524 else
525 *details += QLatin1String("<p>") + result.message();
526
527 if (!result.serverMessage().isEmpty())
528 *details += QLatin1String("<p><b>") + kdb::tr("Message from server:")
529 + QLatin1String("</b> ") + result.serverMessage();
530 if (!result.recentSqlString().isEmpty())
531 *details += QLatin1String("<p><b>") + kdb::tr("SQL statement:")
532 + QString::fromLatin1("</b> <tt>%1</tt>").arg(result.recentSqlString().toString());
533 int serverErrorCode = 0;
534 QString serverResultName;
535 if (result.isError()) {
536 serverErrorCode = result.serverErrorCode();
537 serverResultName = resultable.serverResultName();
538 }
539 if ( !details->isEmpty()
540 && ( !result.serverMessage().isEmpty()
541 || !result.recentSqlString().isEmpty()
542 || !serverResultName.isEmpty()
543 || serverErrorCode != 0)
544 )
545 {
546 *details += (QLatin1String("<p><b>") + kdb::tr("Server result code:")
547 + QLatin1String("</b> ") + QString::number(serverErrorCode));
548 if (!serverResultName.isEmpty()) {
549 *details += QString::fromLatin1(" (%1)").arg(serverResultName);
550 }
551 }
552 else {
553 if (!serverResultName.isEmpty()) {
554 *details += (QLatin1String("<p><b>") + kdb::tr("Server result:")
555 + QLatin1String("</b> ") + serverResultName);
556 }
557 }
558
559 if (!details->isEmpty() && !details->startsWith(QLatin1String("<qt>"))) {
560 if (!details->startsWith(QLatin1String("<p>")))
561 details->prepend(QLatin1String("<p>"));
562 }
563}
564
565void KDb::getHTMLErrorMesage(const KDbResultable& resultable, QString *msg)
566{
567 getHTMLErrorMesage(resultable, msg, msg);
568}
569
571{
572 if (!info) {
573 kdbWarning() << "Missing 'info' parameter";
574 return;
575 }
576 getHTMLErrorMesage(resultable, &info->message, &info->description);
577}
578
579tristate KDb::idForObjectName(KDbConnection* conn, int *id, const QString& objName, int objType)
580{
581 return conn
582 ? conn->querySingleNumber(
583 KDbEscapedString("SELECT o_id FROM kexi__objects WHERE o_name=%1 AND o_type=%2")
584 .arg(conn->escapeString(objName))
585 .arg(objType),
586 id)
587 : false;
588}
589
590//-----------------------------------------
591
593 KDbMessageHandler *msgHandler)
594{
595 ConnectionTestDialog dlg(data, msgHandler, parent);
596 const int result = dlg.exec();
597 if (dlg.wasCanceled()) {
598 return cancelled;
599 }
600 return result == QDialog::Accepted;
601}
602
604 QString *tableName, QString *fieldName,
606{
607 if (!tableName || !fieldName) {
608 return false;
609 }
610 const int id = string.indexOf(QLatin1Char('.'));
611 if (option & SetFieldNameIfNoTableName && id == -1) {
612 tableName->clear();
613 *fieldName = string;
614 return !fieldName->isEmpty();
615 }
616 if (id <= 0 || id == int(string.length() - 1))
617 return false;
618 *tableName = string.left(id);
619 *fieldName = string.mid(id + 1);
620 return !tableName->isEmpty() && !fieldName->isEmpty();
621}
622
624{
625//! @todo add check for decimal type as well
626 return KDbField::isFPNumericType(type);
627}
628
629inline static QString numberToString(double value, int decimalPlaces, const QLocale *locale)
630{
631//! @todo round?
632 QString result;
633 if (decimalPlaces == 0) {
634 result = locale ? locale->toString(qlonglong(value))
635 : QString::number(qlonglong(value));
636 } else {
637 const int realDecimalPlaces = decimalPlaces < 0 ? 10 : decimalPlaces;
638 result = locale ? locale->toString(value, 'f', realDecimalPlaces)
639 : QString::number(value, 'f', realDecimalPlaces);
640 if (decimalPlaces < 0) { // cut off zeros
641 int i = result.length() - 1;
642 while (i > 0 && result[i] == QLatin1Char('0')) {
643 i--;
644 }
645 if (result[i].isDigit()) {// last digit
646 ++i;
647 }
648 result.truncate(i);
649 }
650 }
651 return result;
652}
653
654QString KDb::numberToString(double value, int decimalPlaces)
655{
656 return ::numberToString(value, decimalPlaces, nullptr);
657}
658
659QString KDb::numberToLocaleString(double value, int decimalPlaces)
660{
661 QLocale defaultLocale;
662 return ::numberToString(value, decimalPlaces, &defaultLocale);
663}
664
665QString KDb::numberToLocaleString(double value, int decimalPlaces, const QLocale &locale)
666{
667 return ::numberToString(value, decimalPlaces, &locale);
668}
669
671{
672 if (type < int(KDbField::InvalidType) || type > int(KDbField::LastType)) {
674 }
675 return static_cast<KDbField::Type>(type);
676}
677
679{
680 if (typeGroup < int(KDbField::InvalidGroup) || typeGroup > int(KDbField::LastTypeGroup)) {
681 return KDbField::InvalidGroup;
682 }
683 return static_cast<KDbField::TypeGroup>(typeGroup);
684}
685
686static bool setIntToFieldType(KDbField *field, const QVariant& value)
687{
688 Q_ASSERT(field);
689 bool ok;
690 const int intType = value.toInt(&ok);
691 if (!ok) {//for sanity
692 kdbWarning() << "Could not convert value" << value << "to field type";
693 return false;
694 }
695 if (KDbField::InvalidType == KDb::intToFieldType(intType)) {//for sanity
696 kdbWarning() << "Invalid field type" << intType;
697 return false;
698 }
699 field->setType((KDbField::Type)intType);
700 return true;
701}
702
703//! @internal for KDb::isBuiltinTableFieldProperty()
704struct KDb_BuiltinFieldProperties {
705 KDb_BuiltinFieldProperties() {
706#define ADD(name) set.insert(name)
707 ADD("type");
708 ADD("primaryKey");
709 ADD("indexed");
710 ADD("autoIncrement");
711 ADD("unique");
712 ADD("notNull");
713 ADD("allowEmpty");
714 ADD("unsigned");
715 ADD("name");
716 ADD("caption");
717 ADD("description");
718 ADD("maxLength");
719 ADD("maxLengthIsDefault");
720 ADD("precision");
721 ADD("defaultValue");
722 ADD("defaultWidth");
723 ADD("visibleDecimalPlaces");
724//! @todo always update this when new builtins appear!
725#undef ADD
726 }
728};
729
730//! for KDb::isBuiltinTableFieldProperty()
731Q_GLOBAL_STATIC(KDb_BuiltinFieldProperties, KDb_builtinFieldProperties)
732
733
734bool KDb::isBuiltinTableFieldProperty(const QByteArray& propertyName)
735{
736 return KDb_builtinFieldProperties->set.contains(propertyName);
737}
738
739static QVariant visibleColumnValue(const KDbLookupFieldSchema *lookup)
740{
741 if (!lookup || lookup->visibleColumns().count() == 1) {
742 if (lookup) {
743 const QList<int> visibleColumns = lookup->visibleColumns();
744 if (!visibleColumns.isEmpty()) {
745 return visibleColumns.first();
746 }
747 }
748 return QVariant();
749 }
750 QList<QVariant> variantList;
751 const QList<int> visibleColumns(lookup->visibleColumns());
752 for(int column : visibleColumns) {
753 variantList.append(column);
754 }
755 return variantList;
756}
757
759{
760 if (!values) {
761 return;
762 }
764 if (lookup) {
765 recordSource = lookup->recordSource();
766 }
767 values->insert("rowSource", lookup ? recordSource.name() : QVariant());
768 values->insert("rowSourceType", lookup ? recordSource.typeName() : QVariant());
769 values->insert("rowSourceValues",
770 (lookup && !recordSource.values().isEmpty()) ? recordSource.values() : QVariant());
771 values->insert("boundColumn", lookup ? lookup->boundColumn() : QVariant());
772 values->insert("visibleColumn", visibleColumnValue(lookup));
773 QList<QVariant> variantList;
774 if (lookup) {
775 const QList<int> columnWidths = lookup->columnWidths();
776 for(const QVariant& variant : columnWidths) {
777 variantList.append(variant);
778 }
779 }
780 values->insert("columnWidths", lookup ? variantList : QVariant());
781 values->insert("showColumnHeaders", lookup ? lookup->columnHeadersVisible() : QVariant());
782 values->insert("listRows", lookup ? lookup->maxVisibleRecords() : QVariant());
783 values->insert("limitToList", lookup ? lookup->limitToList() : QVariant());
784 values->insert("displayWidget", lookup ? int(lookup->displayWidget()) : QVariant());
785}
786
788{
789 if (!values) {
790 return;
791 }
792 values->clear();
793 // normal values
794 values->insert("type", field.type());
795 const KDbField::Constraints constraints = field.constraints();
796 values->insert("primaryKey", constraints.testFlag(KDbField::PrimaryKey));
797 values->insert("indexed", constraints.testFlag(KDbField::Indexed));
798 values->insert("autoIncrement", KDbField::isAutoIncrementAllowed(field.type())
799 && constraints.testFlag(KDbField::AutoInc));
800 values->insert("unique", constraints.testFlag(KDbField::Unique));
801 values->insert("notNull", constraints.testFlag(KDbField::NotNull));
802 values->insert("allowEmpty", !constraints.testFlag(KDbField::NotEmpty));
803 const KDbField::Options options = field.options();
804 values->insert("unsigned", options.testFlag(KDbField::Unsigned));
805 values->insert("name", field.name());
806 values->insert("caption", field.caption());
807 values->insert("description", field.description());
808 values->insert("maxLength", field.maxLength());
809 values->insert("maxLengthIsDefault", field.maxLengthStrategy() & KDbField::DefaultMaxLength);
810 values->insert("precision", field.precision());
811 values->insert("defaultValue", field.defaultValue());
812//! @todo IMPORTANT: values->insert("defaultWidth", field.defaultWidth());
814 values->insert("visibleDecimalPlaces", field.defaultValue());
815 }
816 // insert lookup-related values
817 const KDbLookupFieldSchema *lookup = field.table()->lookupFieldSchema(field);
818 KDb::getProperties(lookup, values);
819}
820
821static bool containsLookupFieldSchemaProperties(const QMap<QByteArray, QVariant>& values)
822{
824 it != values.constEnd(); ++it)
825 {
826 if (KDb::isLookupFieldSchemaProperty(it.key())) {
827 return true;
828 }
829 }
830 return false;
831}
832
834{
835 if (!field) {
836 return false;
837 }
839 if ((it = values.find("type")) != values.constEnd()) {
840 if (!setIntToFieldType(field, *it))
841 return false;
842 }
843
844#define SET_BOOLEAN_FLAG(flag, value) { \
845 constraints |= KDbField::flag; \
846 if (!value) \
847 constraints ^= KDbField::flag; \
848 }
849
850 KDbField::Constraints constraints = field->constraints();
851 bool ok = true;
852 if ((it = values.find("primaryKey")) != values.constEnd())
853 SET_BOOLEAN_FLAG(PrimaryKey, (*it).toBool());
854 if ((it = values.find("indexed")) != values.constEnd())
855 SET_BOOLEAN_FLAG(Indexed, (*it).toBool());
856 if ((it = values.find("autoIncrement")) != values.constEnd()
858 SET_BOOLEAN_FLAG(AutoInc, (*it).toBool());
859 if ((it = values.find("unique")) != values.constEnd())
860 SET_BOOLEAN_FLAG(Unique, (*it).toBool());
861 if ((it = values.find("notNull")) != values.constEnd())
862 SET_BOOLEAN_FLAG(NotNull, (*it).toBool());
863 if ((it = values.find("allowEmpty")) != values.constEnd())
864 SET_BOOLEAN_FLAG(NotEmpty, !(*it).toBool());
865 field->setConstraints(constraints);
866
867 KDbField::Options options;
868 if ((it = values.find("unsigned")) != values.constEnd()) {
869 options |= KDbField::Unsigned;
870 if (!(*it).toBool())
871 options ^= KDbField::Unsigned;
872 }
873 field->setOptions(options);
874
875 if ((it = values.find("name")) != values.constEnd())
876 field->setName((*it).toString());
877 if ((it = values.find("caption")) != values.constEnd())
878 field->setCaption((*it).toString());
879 if ((it = values.find("description")) != values.constEnd())
880 field->setDescription((*it).toString());
881 if ((it = values.find("maxLength")) != values.constEnd())
882 field->setMaxLength((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok));
883 if (!ok)
884 return false;
885 if ((it = values.find("maxLengthIsDefault")) != values.constEnd()
886 && (*it).toBool())
887 {
889 }
890 if ((it = values.find("precision")) != values.constEnd())
891 field->setPrecision((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok));
892 if (!ok)
893 return false;
894 if ((it = values.find("defaultValue")) != values.constEnd())
895 field->setDefaultValue(*it);
896//! @todo IMPORTANT: defaultWidth
897#if 0
898 if ((it = values.find("defaultWidth")) != values.constEnd())
899 field.setDefaultWidth((*it).isNull() ? 0/*default*/ : (*it).toInt(&ok));
900 if (!ok)
901 return false;
902#endif
903
904 // -- extended properties
905 if ((it = values.find("visibleDecimalPlaces")) != values.constEnd()
907 field->setVisibleDecimalPlaces((*it).isNull() ? -1/*default*/ : (*it).toInt(&ok));
908 if (!ok)
909 return false;
910
911 if (field->table() && containsLookupFieldSchemaProperties(values)) {
912 KDbLookupFieldSchema *lookup = field->table()->lookupFieldSchema(*field);
914 if (!lookup) { // create lookup if needed
915 createdLookup.reset(lookup = new KDbLookupFieldSchema());
916 }
917 if (lookup->setProperties(values)) {
918 if (createdLookup) {
919 if (field->table()->setLookupFieldSchema(field->name(), lookup)) {
920 createdLookup.take(); // ownership passed
921 lookup = nullptr;
922 }
923 }
924 }
925 }
926
927 return true;
928#undef SET_BOOLEAN_FLAG
929}
930
931//! @internal for isExtendedTableProperty()
932struct KDb_ExtendedProperties {
933 KDb_ExtendedProperties() {
934#define ADD(name) set.insert( name )
935 ADD("visibledecimalplaces");
936 ADD("rowsource");
937 ADD("rowsourcetype");
938 ADD("rowsourcevalues");
939 ADD("boundcolumn");
940 ADD("visiblecolumn");
941 ADD("columnwidths");
942 ADD("showcolumnheaders");
943 ADD("listrows");
944 ADD("limittolist");
945 ADD("displaywidget");
946#undef ADD
947 }
949};
950
951//! for isExtendedTableProperty()
952Q_GLOBAL_STATIC(KDb_ExtendedProperties, KDb_extendedProperties)
953
954bool KDb::isExtendedTableFieldProperty(const QByteArray& propertyName)
955{
956 return KDb_extendedProperties->set.contains(QByteArray(propertyName).toLower());
957}
958
959//! @internal for isLookupFieldSchemaProperty()
960struct KDb_LookupFieldSchemaProperties {
961 KDb_LookupFieldSchemaProperties() {
963 KDb::getProperties(nullptr, &tmp);
965 it != tmp.constEnd(); ++it)
966 {
967 set.insert(it.key().toLower());
968 }
969 }
971};
972
973//! for isLookupFieldSchemaProperty()
974Q_GLOBAL_STATIC(KDb_LookupFieldSchemaProperties, KDb_lookupFieldSchemaProperties)
975
976bool KDb::isLookupFieldSchemaProperty(const QByteArray& propertyName)
977{
978 return KDb_lookupFieldSchemaProperties->set.contains(propertyName.toLower());
979}
980
981bool KDb::setFieldProperty(KDbField *field, const QByteArray& propertyName, const QVariant& value)
982{
983 if (!field) {
984 return false;
985 }
986#define SET_BOOLEAN_FLAG(flag, value) { \
987 constraints |= KDbField::flag; \
988 if (!value) \
989 constraints ^= KDbField::flag; \
990 field->setConstraints( constraints ); \
991 return true; \
992 }
993#define GET_INT(method) { \
994 const int ival = value.toInt(&ok); \
995 if (!ok) \
996 return false; \
997 field->method( ival ); \
998 return true; \
999 }
1000
1001 if (propertyName.isEmpty())
1002 return false;
1003
1004 bool ok;
1005 if (KDb::isExtendedTableFieldProperty(propertyName)) {
1006 //a little speedup: identify extended property in O(1)
1007 if ("visibleDecimalPlaces" == propertyName
1009 GET_INT(setVisibleDecimalPlaces);
1010 }
1011 else if (KDb::isLookupFieldSchemaProperty(propertyName)) {
1012 if (!field->table()) {
1013 kdbWarning() << "Could not set" << propertyName << "property - no table assigned for field";
1014 } else {
1015 KDbLookupFieldSchema *lookup = field->table()->lookupFieldSchema(*field);
1016 const bool createLookup = !lookup;
1017 if (createLookup) // create lookup if needed
1018 lookup = new KDbLookupFieldSchema();
1019 if (lookup->setProperty(propertyName, value)) {
1020 if (createLookup)
1021 field->table()->setLookupFieldSchema(field->name(), lookup);
1022 return true;
1023 }
1024 if (createLookup)
1025 delete lookup; // not set, delete
1026 }
1027 }
1028 } else {//non-extended
1029 if ("type" == propertyName)
1030 return setIntToFieldType(field, value);
1031
1032 KDbField::Constraints constraints = field->constraints();
1033 if ("primaryKey" == propertyName)
1034 SET_BOOLEAN_FLAG(PrimaryKey, value.toBool());
1035 if ("indexed" == propertyName)
1036 SET_BOOLEAN_FLAG(Indexed, value.toBool());
1037 if ("autoIncrement" == propertyName
1039 SET_BOOLEAN_FLAG(AutoInc, value.toBool());
1040 if ("unique" == propertyName)
1041 SET_BOOLEAN_FLAG(Unique, value.toBool());
1042 if ("notNull" == propertyName)
1043 SET_BOOLEAN_FLAG(NotNull, value.toBool());
1044 if ("allowEmpty" == propertyName)
1045 SET_BOOLEAN_FLAG(NotEmpty, !value.toBool());
1046
1047 KDbField::Options options;
1048 if ("unsigned" == propertyName) {
1049 options |= KDbField::Unsigned;
1050 if (!value.toBool())
1051 options ^= KDbField::Unsigned;
1052 field->setOptions(options);
1053 return true;
1054 }
1055
1056 if ("name" == propertyName) {
1057 if (value.toString().isEmpty())
1058 return false;
1059 field->setName(value.toString());
1060 return true;
1061 }
1062 if ("caption" == propertyName) {
1063 field->setCaption(value.toString());
1064 return true;
1065 }
1066 if ("description" == propertyName) {
1067 field->setDescription(value.toString());
1068 return true;
1069 }
1070 if ("maxLength" == propertyName)
1071 GET_INT(setMaxLength);
1072 if ("maxLengthIsDefault" == propertyName) {
1074 }
1075 if ("precision" == propertyName)
1076 GET_INT(setPrecision);
1077 if ("defaultValue" == propertyName) {
1078 field->setDefaultValue(value);
1079 return true;
1080 }
1081
1082//! @todo IMPORTANT: defaultWidth
1083#if 0
1084 if ("defaultWidth" == propertyName)
1085 GET_INT(setDefaultWidth);
1086#endif
1087 // last chance that never fails: custom field property
1088 field->setCustomProperty(propertyName, value);
1089 }
1090
1091 kdbWarning() << "Field property" << propertyName << "not found!";
1092 return false;
1093#undef SET_BOOLEAN_FLAG
1094#undef GET_INT
1095}
1096
1098{
1099 QByteArray valueType = node.nodeName().toLatin1();
1100 if (valueType.isEmpty() || valueType != "number") {
1101 if (ok)
1102 *ok = false;
1103 return 0;
1104 }
1105 const QString text(QDomNode(node).toElement().text());
1106 int val = text.toInt(ok);
1107 return val;
1108}
1109
1111{
1112 QByteArray valueType = node.nodeName().toLatin1();
1113 if (valueType != "string") {
1114 if (ok)
1115 *ok = false;
1116 return QString();
1117 }
1118 if (ok)
1119 *ok = true;
1120 return QDomNode(node).toElement().text();
1121}
1122
1124{
1125 QByteArray valueType = node.nodeName().toLatin1();
1126 if (valueType.isEmpty()) {
1127 if (ok)
1128 *ok = false;
1129 return QVariant();
1130 }
1131 if (ok)
1132 *ok = true;
1133 const QString text(QDomNode(node).toElement().text());
1134 bool _ok;
1135 if (valueType == "string") {
1136 return text;
1137 }
1138 else if (valueType == "cstring") {
1139 return text.toLatin1();
1140 }
1141 else if (valueType == "number") { // integer or double
1142 if (text.indexOf(QLatin1Char('.')) != -1) {
1143 double val = text.toDouble(&_ok);
1144 if (_ok)
1145 return val;
1146 }
1147 else {
1148 const int val = text.toInt(&_ok);
1149 if (_ok)
1150 return val;
1151 const qint64 valLong = text.toLongLong(&_ok);
1152 if (_ok)
1153 return valLong;
1154 }
1155 }
1156 else if (valueType == "bool") {
1157 return text.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0
1158 || text == QLatin1String("1");
1159 }
1160 else {
1161//! @todo add more QVariant types
1162 kdbWarning() << "Unknown property type" << valueType;
1163 }
1164 if (ok)
1165 *ok = false;
1166 return QVariant();
1167}
1168
1170 const QString& elementName, int value)
1171{
1172 if (!doc || !parentEl || elementName.isEmpty()) {
1173 return QDomElement();
1174 }
1175 QDomElement el(doc->createElement(elementName));
1176 parentEl->appendChild(el);
1177 QDomElement numberEl(doc->createElement(QLatin1String("number")));
1178 el.appendChild(numberEl);
1179 numberEl.appendChild(doc->createTextNode(QString::number(value)));
1180 return el;
1181}
1182
1184 const QString& elementName, bool value)
1185{
1186 if (!doc || !parentEl || elementName.isEmpty()) {
1187 return QDomElement();
1188 }
1189 QDomElement el(doc->createElement(elementName));
1190 parentEl->appendChild(el);
1191 QDomElement numberEl(doc->createElement(QLatin1String("bool")));
1192 el.appendChild(numberEl);
1193 numberEl.appendChild(doc->createTextNode(
1194 value ? QLatin1String("true") : QLatin1String("false")));
1195 return el;
1196}
1197
1198//! @internal Used in KDb::emptyValueForFieldType()
1199struct KDb_EmptyValueForFieldTypeCache {
1200 KDb_EmptyValueForFieldTypeCache()
1201 : values(int(KDbField::LastType) + 1) {
1202#define ADD(t, value) values.insert(t, value);
1203 ADD(KDbField::Byte, 0);
1204 ADD(KDbField::ShortInteger, 0);
1205 ADD(KDbField::Integer, 0);
1206 ADD(KDbField::BigInteger, 0);
1207 ADD(KDbField::Boolean, false);
1208 ADD(KDbField::Float, 0.0);
1209 ADD(KDbField::Double, 0.0);
1210//! @todo ok? we have no better defaults
1211 ADD(KDbField::Text, QLatin1String(" "));
1213 ADD(KDbField::BLOB, QByteArray());
1214#undef ADD
1215 }
1216 QVector<QVariant> values;
1217};
1218
1219//! Used in KDb::emptyValueForFieldType()
1220Q_GLOBAL_STATIC(KDb_EmptyValueForFieldTypeCache, KDb_emptyValueForFieldTypeCache)
1221
1222QVariant KDb::emptyValueForFieldType(KDbField::Type type)
1223{
1224 const QVariant val(KDb_emptyValueForFieldTypeCache->values.at(
1225 (type <= KDbField::LastType) ? type : KDbField::InvalidType));
1226 if (!val.isNull())
1227 return val;
1228 else { //special cases
1229 if (type == KDbField::Date)
1230 return QDate::currentDate();
1231 if (type == KDbField::DateTime)
1233 if (type == KDbField::Time)
1234 return QTime::currentTime();
1235 }
1236 kdbWarning() << "No empty value for field type" << KDbField::typeName(type);
1237 return QVariant();
1238}
1239
1240//! @internal Used in KDb::notEmptyValueForFieldType()
1241struct KDb_NotEmptyValueForFieldTypeCache {
1242 KDb_NotEmptyValueForFieldTypeCache()
1243 : values(int(KDbField::LastType) + 1) {
1244#define ADD(t, value) values.insert(t, value);
1245 // copy most of the values
1246 for (int i = int(KDbField::InvalidType) + 1; i <= KDbField::LastType; i++) {
1247 if (i == KDbField::Date || i == KDbField::DateTime || i == KDbField::Time)
1248 continue; //'current' value will be returned
1249 if (i == KDbField::Text || i == KDbField::LongText) {
1250 ADD(i, QVariant(QLatin1String("")));
1251 continue;
1252 }
1253 if (i == KDbField::BLOB) {
1254//! @todo blobs will contain other MIME types too
1255 QByteArray ba;
1256//! @todo port to Qt4
1257#if 0
1258 QBuffer buffer(&ba);
1259 buffer.open(QIODevice::WriteOnly);
1260 QPixmap pm(SmallIcon("document-new"));
1261 pm.save(&buffer, "PNG"/*! @todo default? */);
1262#endif
1263 ADD(i, ba);
1264 continue;
1265 }
1267 }
1268#undef ADD
1269 }
1270 QVector<QVariant> values;
1271};
1272//! Used in KDb::notEmptyValueForFieldType()
1273Q_GLOBAL_STATIC(KDb_NotEmptyValueForFieldTypeCache, KDb_notEmptyValueForFieldTypeCache)
1274
1275QVariant KDb::notEmptyValueForFieldType(KDbField::Type type)
1276{
1277 const QVariant val(KDb_notEmptyValueForFieldTypeCache->values.at(
1278 (type <= KDbField::LastType) ? type : KDbField::InvalidType));
1279 if (!val.isNull())
1280 return val;
1281 else { //special cases
1282 if (type == KDbField::Date)
1283 return QDate::currentDate();
1284 if (type == KDbField::DateTime)
1286 if (type == KDbField::Time)
1287 return QTime::currentTime();
1288 }
1289 kdbWarning() << "No non-empty value for field type" << KDbField::typeName(type);
1290 return QVariant();
1291}
1292
1293//! @internal @return nestimated new length after escaping of string @a string
1294template<typename T>
1295inline static int estimatedNewLength(const T &string, bool addQuotes)
1296{
1297 if (string.length() < 10)
1298 return string.length() * 2 + (addQuotes ? 2 : 0);
1299 return string.length() * 3 / 2;
1300}
1301
1302//! @internal @return @a string string with applied KDbSQL identifier escaping.
1303//! If @a addQuotes is true, '"' characer is prepended and appended.
1304template<typename T, typename Latin1StringType, typename Latin1CharType, typename CharType>
1305inline static T escapeIdentifier(const T& string, bool addQuotes)
1306{
1307 const Latin1CharType quote('"');
1308 // create
1309 Latin1StringType escapedQuote("\"\"");
1310 T newString;
1311 newString.reserve(estimatedNewLength(string, addQuotes));
1312 if (addQuotes) {
1313 newString.append(quote);
1314 }
1315 for (int i = 0; i < string.length(); i++) {
1316 const CharType c = string.at(i);
1317 if (c == quote)
1318 newString.append(escapedQuote);
1319 else
1320 newString.append(c);
1321 }
1322 if (addQuotes) {
1323 newString.append(quote);
1324 }
1325 newString.squeeze();
1326 return newString;
1327}
1328
1329static bool shouldAddQuotesToIdentifier(const QByteArray& string)
1330{
1331 return !string.isEmpty() && (!KDb::isIdentifier(string) || KDb::isKDbSqlKeyword(string));
1332}
1333
1335{
1336 return ::escapeIdentifier<QString, QLatin1String, QLatin1Char, QChar>(
1337 string, shouldAddQuotesToIdentifier(string.toLatin1()));
1338}
1339
1341{
1342 return ::escapeIdentifier<QByteArray, QByteArray, char, char>(
1343 string, shouldAddQuotesToIdentifier(string));
1344}
1345
1347{
1348 return ::escapeIdentifier<QString, QLatin1String, QLatin1Char, QChar>(string, true);
1349}
1350
1352{
1353 return ::escapeIdentifier<QByteArray, QByteArray, char, char>(string, true);
1354}
1355
1357{
1358 const QLatin1Char quote('\'');
1359 // find out the length ot the destination string
1360 // create
1361 QString newString(quote);
1362 newString.reserve(estimatedNewLength(string, true));
1363 for (int i = 0; i < string.length(); i++) {
1364 const QChar c = string.at(i);
1365 const ushort unicode = c.unicode();
1366 if (unicode == quote)
1367 newString.append(QLatin1String("''"));
1368 else if (unicode == '\t')
1369 newString.append(QLatin1String("\\t"));
1370 else if (unicode == '\\')
1371 newString.append(QLatin1String("\\\\"));
1372 else if (unicode == '\n')
1373 newString.append(QLatin1String("\\n"));
1374 else if (unicode == '\r')
1375 newString.append(QLatin1String("\\r"));
1376 else if (unicode == '\0')
1377 newString.append(QLatin1String("\\0"));
1378 else
1379 newString.append(c);
1380 }
1381 newString.append(QLatin1Char(quote));
1382 return newString;
1383}
1384
1386{
1387 return drv ? drv->escapeString(string) : KDbEscapedString(KDb::escapeString(string));
1388}
1389
1391{
1392 return conn ? conn->escapeString(string) : KDbEscapedString(KDb::escapeString(string));
1393}
1394
1395//! @see handleHex()
1396const int CODE_POINT_DIGITS = std::numeric_limits<int>::max();
1397//! @see handleHex()
1398const int MAX_CODE_POINT_VALUE = 0x10FFFF;
1399
1400//! @internal Decodes hex of length @a digits for handleXhh(), handleUxxxx() and handleUcodePoint()
1401//! If @a digits is CODE_POINT_DIGITS, any number of hex digits is decoded until '}' character
1402//! is found (error if not found), and the function succeeds when the resulting number
1403//! is not larger than MAX_CODE_POINT_VALUE.
1404//! If @a digits is smaller than CODE_POINT_DIGITS the function succeeds only if exactly @a digits
1405//! number of digits has been found.
1406//! @return -1 on error (when invalid character found or on missing character
1407//! or if the resulting number is too large)
1408//! @see KDb::unescapeString()
1409static int handleHex(QString *result, int *from, int stringLen, int *errorPosition, int digits)
1410{
1411 int digit = 0;
1412 for (int i=0; i<digits; ++i) {
1413 if ((*from + 1) >= stringLen) { // unfinished
1414 if (errorPosition) {
1415 *errorPosition = *from;
1416 }
1417 return -1;
1418 }
1419 ++(*from);
1420 if (digits == CODE_POINT_DIGITS && (*result)[*from] == QLatin1Char('}')) {
1421 // special case: code point character decoded
1422 if (i == 0) {
1423 if (errorPosition) {
1424 *errorPosition = *from;
1425 }
1426 return -1;
1427 }
1428 return digit;
1429 }
1430 const unsigned char d = hexDigitToInt((*result)[*from].toLatin1());
1431 if (d == 0xFF) { // unfinished or wrong character
1432 if (errorPosition) {
1433 *errorPosition = *from;
1434 }
1435 return -1;
1436 }
1437 digit = (digit << 4) + d;
1438 if (digits == CODE_POINT_DIGITS) {
1439 if (digit > MAX_CODE_POINT_VALUE) { // special case: exceeded limit of code point
1440 if (errorPosition) {
1441 *errorPosition = *from;
1442 }
1443 return -1;
1444 }
1445 }
1446 }
1447 return digit;
1448}
1449
1450//! @internal Handles \xhh format for handleEscape()
1451//! Assumption: the @a *from points to "x" in the "\x"
1452//! @see KDb::unescapeString()
1453static bool handleXhh(QString *result, int *from, int to, int stringLen, int *errorPosition)
1454{
1455 const int intDigit = handleHex(result, from, stringLen, errorPosition, 2);
1456 if (intDigit == -1) {
1457 return false;
1458 }
1459 (*result)[to] = QChar(static_cast<unsigned char>(intDigit), 0);
1460 return true;
1461}
1462
1463//! @internal Handles \uxxxx format for handleEscape()
1464//! Assumption: the @a *from points to the "u" in the "\u".
1465//! @see KDb::unescapeString()
1466static bool handleUxxxx(QString *result, int *from, int to, int stringLen, int *errorPosition)
1467{
1468 const int intDigit = handleHex(result, from, stringLen, errorPosition, 4);
1469 if (intDigit == -1) {
1470 return false;
1471 }
1472 (*result)[to] = QChar(static_cast<unsigned short>(intDigit));
1473 return true;
1474}
1475
1476//! @internal Handles \u{xxxxxx} format for handleEscape()
1477//! Assumption: the @a *from points to the "{" in the "\u{".
1478//! @see KDb::unescapeString()
1479static bool handleUcodePoint(QString *result, int *from, int to, int stringLen, int *errorPosition)
1480{
1481 const int intDigit = handleHex(result, from, stringLen, errorPosition, CODE_POINT_DIGITS);
1482 if (intDigit == -1) {
1483 return false;
1484 }
1485 (*result)[to] = QChar(intDigit);
1486 return true;
1487}
1488
1489//! @internal Handles escaped character @a c2 for KDb::unescapeString()
1490//! Updates @a result
1491//! @return true on success
1492static bool handleEscape(QString *result, int *from, int *to, int stringLen, int *errorPosition)
1493{
1494 const QCharRef c2 = (*result)[*from];
1495 if (c2 == QLatin1Char('x')) { // \xhh
1496 if (!handleXhh(result, from, *to, stringLen, errorPosition)) {
1497 return false;
1498 }
1499 } else if (c2 == QLatin1Char('u')) { // \u
1500 if ((*from + 1) >= stringLen) { // unfinished
1501 if (errorPosition) {
1502 *errorPosition = *from;
1503 }
1504 return false;
1505 }
1506 ++(*from);
1507 const QCharRef c3 = (*result)[*from];
1508 if (c3 == QLatin1Char('{')) { // \u{
1509 if (!handleUcodePoint(result, from, *to, stringLen, errorPosition)) {
1510 return false;
1511 }
1512 } else {
1513 --(*from);
1514 if (!handleUxxxx(result, from, *to, stringLen, errorPosition)) {
1515 return false;
1516 }
1517 }
1518#define _RULE(in, out) \
1519 } else if (c2 == QLatin1Char(in)) { \
1520 (*result)[*to] = QLatin1Char(out);
1521 _RULE('0', '\0') _RULE('b', '\b') _RULE('f', '\f') _RULE('n', '\n')
1522 _RULE('r', '\r') _RULE('t', '\t') _RULE('v', '\v')
1523#undef _RULE
1524 } else { // \ ' " ? % _ and any other without special meaning can be escaped: just skip "\"
1525 (*result)[*to] = c2;
1526 }
1527 return true;
1528}
1529
1530QString KDb::unescapeString(const QString& string, char quote, int *errorPosition)
1531{
1532 if (quote != '\'' && quote != '\"') {
1533 if (errorPosition) {
1534 *errorPosition = 0;
1535 }
1536 return QString();
1537 }
1538 const QLatin1Char quoteChar(quote);
1539 if (string.isEmpty()
1540 || (!string.contains(QLatin1Char('\\')) && !string.contains(quoteChar)))
1541 {
1542 if (errorPosition) {
1543 *errorPosition = -1;
1544 }
1545 return string; // optimization: there are no escapes and quotes
1546 }
1547 QString result(string);
1548 const int stringLen = string.length();
1549 int from = 0;
1550 int to = 0;
1551 bool doubleQuoteExpected = false;
1552 while (from < stringLen) {
1553 const QCharRef c = result[from];
1554 if (doubleQuoteExpected) {
1555 if (c == quoteChar) {
1556 result[to] = c;
1557 doubleQuoteExpected = false;
1558 } else {
1559 // error: missing second quote
1560 if (errorPosition) {
1561 *errorPosition = from - 1; // -1 because error is at prev. char
1562 }
1563 return QString();
1564 }
1565 } else if (c == quoteChar) {
1566 doubleQuoteExpected = true;
1567 ++from;
1568 continue;
1569 } else if (c == QLatin1Char('\\')) { // escaping
1570 if ((from + 1) >= stringLen) { // ignore unfinished '\'
1571 break;
1572 }
1573 ++from;
1574 if (!handleEscape(&result, &from, &to, stringLen, errorPosition)) {
1575 return QString();
1576 }
1577 } else { // normal character: skip
1578 result[to] = result[from];
1579 }
1580 ++from;
1581 ++to;
1582 }
1583 if (doubleQuoteExpected) { // error: string ends with a single quote
1584 if (errorPosition) {
1585 *errorPosition = from - 1;
1586 }
1587 return QString();
1588 }
1589 if (errorPosition) {
1590 *errorPosition = -1;
1591 }
1592 result.truncate(to);
1593 return result;
1594}
1595
1596//! @return hex digit '0'..'F' for integer number 0..15
1597inline static char intToHexDigit(unsigned char val)
1598{
1599 return (val < 10) ? ('0' + val) : ('A' + (val - 10));
1600}
1601
1602QString KDb::escapeBLOB(const QByteArray& array, BLOBEscapingType type)
1603{
1604 const int size = array.size();
1605 if (size == 0 && type == BLOBEscapingType::ZeroXHex)
1606 return QString();
1607 int escaped_length = size * 2;
1608 if (type == BLOBEscapingType::ZeroXHex || type == BLOBEscapingType::Octal)
1609 escaped_length += 2/*0x or X'*/;
1610 else if (type == BLOBEscapingType::XHex)
1611 escaped_length += 3; //X' + '
1612 else if (type == BLOBEscapingType::ByteaHex)
1613 escaped_length += (4 + 8); // E'\x + '::bytea
1614
1615 QString str;
1616 str.reserve(escaped_length);
1617 if (str.capacity() < escaped_length) {
1618 kdbWarning() << "Not enough memory (cannot allocate" << escaped_length << "characters)";
1619 return QString();
1620 }
1621 if (type == BLOBEscapingType::XHex)
1622 str = QString::fromLatin1("X'");
1623 else if (type == BLOBEscapingType::ZeroXHex)
1624 str = QString::fromLatin1("0x");
1625 else if (type == BLOBEscapingType::Octal)
1626 str = QString::fromLatin1("'");
1627 else if (type == BLOBEscapingType::ByteaHex)
1628 str = QString::fromLatin1("E'\\\\x");
1629
1630 if (type == BLOBEscapingType::Octal) {
1631 // only escape nonprintable characters as in Table 8-7:
1632 // https://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
1633 // i.e. escape for bytes: < 32, >= 127, 39 ('), 92(\‍).
1634 for (int i = 0; i < size; i++) {
1635 const unsigned char val = array[i];
1636 if (val < 32 || val >= 127 || val == 39 || val == 92) {
1637 str.append(QLatin1Char('\\'));
1638 str.append(QLatin1Char('\\'));
1639 str.append(QChar::fromLatin1('0' + val / 64));
1640 str.append(QChar::fromLatin1('0' + (val % 64) / 8));
1641 str.append(QChar::fromLatin1('0' + val % 8));
1642 } else {
1643 str.append(QChar::fromLatin1(val));
1644 }
1645 }
1646 } else {
1647 for (int i = 0; i < size; i++) {
1648 const unsigned char val = array[i];
1649 str.append(QChar::fromLatin1(intToHexDigit(val / 16)));
1650 str.append(QChar::fromLatin1(intToHexDigit(val % 16)));
1651 }
1652 }
1653 if (type == BLOBEscapingType::XHex || type == BLOBEscapingType::Octal) {
1654 str.append(QLatin1Char('\''));
1655 } else if (type == BLOBEscapingType::ByteaHex) {
1656 str.append(QLatin1String("\'::bytea"));
1657 }
1658 return str;
1659}
1660
1661QByteArray KDb::pgsqlByteaToByteArray(const char* data, int length)
1662{
1663 if (!data) {
1664 return QByteArray();
1665 }
1666 QByteArray array;
1667 int output = 0;
1668 if (length < 0) {
1669 length = qstrlen(data);
1670 }
1671 for (int pass = 0; pass < 2; pass++) {//2 passes to avoid allocating buffer twice:
1672 // 0: count #of chars; 1: copy data
1673 const char* s = data;
1674 const char* end = s + length;
1675 if (pass == 1) {
1676 //kdbDebug() << "processBinaryData(): real size == " << output;
1677 array.resize(output);
1678 output = 0;
1679 }
1680 for (int input = 0; s < end; output++) {
1681 // kdbDebug()<<(int)s[0]<<" "<<(int)s[1]<<" "<<(int)s[2]<<" "<<(int)s[3]<<" "<<(int)s[4];
1682 if (s[0] == '\\' && (s + 1) < end) {
1683 //special cases as in https://www.postgresql.org/docs/8.1/interactive/datatype-binary.html
1684 if (s[1] == '\'') {// \'
1685 if (pass == 1)
1686 array[output] = '\'';
1687 s += 2;
1688 } else if (s[1] == '\\') { // 2 backslashes
1689 if (pass == 1)
1690 array[output] = '\\';
1691 s += 2;
1692 } else if ((input + 3) < length) {// \\xyz where xyz are 3 octal digits
1693 if (pass == 1)
1694 array[output] = char((int(s[1] - '0') * 8 + int(s[2] - '0')) * 8 + int(s[3] - '0'));
1695 s += 4;
1696 } else {
1697 kdbWarning() << "Missing octal value after backslash";
1698 s++;
1699 }
1700 } else {
1701 if (pass == 1)
1702 array[output] = s[0];
1703 s++;
1704 }
1705 // kdbDebug()<<output<<": "<<(int)array[output];
1706 }
1707 }
1708 return array;
1709}
1710
1711QByteArray KDb::xHexToByteArray(const char* data, int length, bool *ok)
1712{
1713 if (length < 0) {
1714 length = qstrlen(data);
1715 }
1716 if (length < 3 || data[0] != 'X' || data[1] != '\'' || data[length-1] != '\'') { // must be at least X''
1717 if (ok) {
1718 *ok = false;
1719 }
1720 return QByteArray();
1721 }
1722 data += 2; // eat X'
1723 length -= 3; // eax X' and '
1724 QByteArray array;
1725 if (!hexToByteArrayInternal(data, length, &array)) {
1726 if (ok) {
1727 *ok = false;
1728 }
1729 array.clear();
1730 }
1731 if (ok) {
1732 *ok = true;
1733 }
1734 return array;
1735}
1736
1737/*! \return byte array converted from \a data of length \a length.
1738 \a data is escaped in format 0x*, where * is one or more bytes in hexadecimal format.
1739 See BLOBEscapingType::ZeroXHex. */
1740QByteArray KDb::zeroXHexToByteArray(const char* data, int length, bool *ok)
1741{
1742 if (length < 0) {
1743 length = qstrlen(data);
1744 }
1745 if (length < 3 || data[0] != '0' || data[1] != 'x') { // must be at least 0xD
1746 if (ok) {
1747 *ok = false;
1748 }
1749 return QByteArray();
1750 }
1751 data += 2; // eat 0x
1752 length -= 2;
1753 QByteArray array;
1754 if (!hexToByteArrayInternal(data, length, &array)) {
1755 if (ok) {
1756 *ok = false;
1757 }
1758 array.clear();
1759 }
1760 if (ok) {
1761 *ok = true;
1762 }
1763 return array;
1764}
1765
1766QList<int> KDb::stringListToIntList(const QStringList &list, bool *ok)
1767{
1768 QList<int> result;
1769 foreach (const QString &item, list) {
1770 int val = item.toInt(ok);
1771 if (ok && !*ok) {
1772 return QList<int>();
1773 }
1774 result.append(val);
1775 }
1776 if (ok) {
1777 *ok = true;
1778 }
1779 return result;
1780}
1781
1782// Based on KConfigGroupPrivate::serializeList() from kconfiggroup.cpp (kdelibs 4)
1784{
1785 QString value;
1786
1787 if (!list.isEmpty()) {
1790
1791 value = QString(*it).replace(QLatin1Char('\\'), QLatin1String("\\\\"))
1792 .replace(QLatin1Char(','), QLatin1String("\\,"));
1793
1794 while (++it != end) {
1795 // In the loop, so it is not done when there is only one element.
1796 // Doing it repeatedly is a pretty cheap operation.
1797 value.reserve(4096);
1798
1799 value += QLatin1Char(',')
1800 + QString(*it).replace(QLatin1Char('\\'), QLatin1String("\\\\"))
1801 .replace(QLatin1Char(','), QLatin1String("\\,"));
1802 }
1803
1804 // To be able to distinguish an empty list from a list with one empty element.
1805 if (value.isEmpty())
1806 value = QLatin1String("\\0");
1807 }
1808
1809 return value;
1810}
1811
1812// Based on KConfigGroupPrivate::deserializeList() from kconfiggroup.cpp (kdelibs 4)
1814{
1815 if (data.isEmpty())
1816 return QStringList();
1817 if (data == QLatin1String("\\0"))
1818 return QStringList(QString());
1819 QStringList value;
1820 QString val;
1821 val.reserve(data.size());
1822 bool quoted = false;
1823 for (int p = 0; p < data.length(); p++) {
1824 if (quoted) {
1825 val += data[p];
1826 quoted = false;
1827 } else if (data[p].unicode() == QLatin1Char('\\')) {
1828 quoted = true;
1829 } else if (data[p].unicode() == QLatin1Char(',')) {
1830 val.squeeze(); // release any unused memory
1831 value.append(val);
1832 val.clear();
1833 val.reserve(data.size() - p);
1834 } else {
1835 val += data[p];
1836 }
1837 }
1838 value.append(val);
1839 return value;
1840}
1841
1842QList<int> KDb::deserializeIntList(const QString &data, bool *ok)
1843{
1845 KDb::deserializeList(data), ok);
1846}
1847
1849 {
1850 if (v.type() == QVariant::ByteArray) {
1852 }
1853 else if (v.type() == QVariant::StringList) {
1854 return serializeList(v.toStringList());
1855 }
1856 return v.toString();
1857}
1858
1859QVariant KDb::stringToVariant(const QString& s, QVariant::Type type, bool* ok)
1860{
1861 if (s.isNull()) {
1862 if (ok)
1863 *ok = true;
1864 return QVariant();
1865 }
1866 switch (type) {
1867 case QVariant::Invalid:
1868 if (ok)
1869 *ok = false;
1870 return QVariant();
1871 case QVariant::ByteArray: {//special case: hex string
1872 const int len = s.length();
1873 QByteArray ba;
1874 ba.resize(len / 2 + len % 2);
1875 for (int i = 0; i < (len - 1); i += 2) {
1876 bool _ok;
1877 int c = s.midRef(i, 2).toInt(&_ok, 16);
1878 if (!_ok) {
1879 if (ok)
1880 *ok = _ok;
1881 kdbWarning() << "Error in digit" << i;
1882 return QVariant();
1883 }
1884 ba[i/2] = (char)c;
1885 }
1886 if (ok)
1887 *ok = true;
1888 return ba;
1889 }
1891 *ok = true;
1892 return KDb::deserializeList(s);
1893 default:;
1894 }
1895
1896 QVariant result(s);
1897 if (!result.convert(type)) {
1898 if (ok)
1899 *ok = false;
1900 return QVariant();
1901 }
1902 if (ok)
1903 *ok = true;
1904 return result;
1905}
1906
1907bool KDb::isDefaultValueAllowed(const KDbField &field)
1908{
1909 return !field.isUniqueKey();
1910}
1911
1912void KDb::getLimitsForFieldType(KDbField::Type type, qlonglong *minValue, qlonglong *maxValue,
1913 Signedness signedness)
1914{
1915 if (!minValue || !maxValue) {
1916 return;
1917 }
1918 switch (type) {
1919 case KDbField::Byte:
1920//! @todo always ok?
1921 *minValue = signedness == KDb::Signed ? -0x80 : 0;
1922 *maxValue = signedness == KDb::Signed ? 0x7F : 0xFF;
1923 break;
1925 *minValue = signedness == KDb::Signed ? -0x8000 : 0;
1926 *maxValue = signedness == KDb::Signed ? 0x7FFF : 0xFFFF;
1927 break;
1928 case KDbField::Integer:
1929 case KDbField::BigInteger: //!< @todo cannot return anything larger?
1930 default:
1931 *minValue = signedness == KDb::Signed ? qlonglong(-0x07FFFFFFF) : qlonglong(0);
1932 *maxValue = signedness == KDb::Signed ? qlonglong(0x07FFFFFFF) : qlonglong(0x0FFFFFFFF);
1933 }
1934}
1935
1937{
1939 return KDbField::InvalidType;
1940 if (t1 == t2)
1941 return t2;
1943 return t1;
1944 if (t1 == KDbField::Integer && t2 != KDbField::BigInteger)
1945 return t1;
1946 if (t1 == KDbField::BigInteger)
1947 return t1;
1948 return KDb::maximumForIntegerFieldTypes(t2, t1); //swap
1949}
1950
1952{
1953 if (KDbField::isNumericType(type))
1954 return KDbField::tr("Number"); //simplify
1955 else if (type == KDbField::BLOB)
1956//! @todo support names of other BLOB subtypes
1957 return KDbField::tr("Image"); //simplify
1958
1960}
1961
1963{
1964 return QLatin1String("application/x-kexiproject-sqlite3");
1965}
1966
1968{
1969 return QLatin1String("org.kde.kdb.sqlite");
1970}
1971
1972// Try to convert from string to type T
1973template <typename T>
1974QVariant convert(T (QString::*ConvertToT)(bool*,int) const, const char *data, int size,
1975 qlonglong minValue, qlonglong maxValue, bool *ok)
1976{
1977 T v = (QString::fromLatin1(data, size).*ConvertToT)(ok, 10);
1978 if (*ok) {
1979 *ok = minValue <= v && v <= maxValue;
1980 }
1981 return KDb::iif(*ok, QVariant(v));
1982}
1983
1984QVariant KDb::cstringToVariant(const char* data, KDbField::Type type, bool *ok, int length,
1985 KDb::Signedness signedness)
1986{
1987 bool tempOk;
1988 bool *thisOk = ok ? ok : &tempOk;
1989 if (type < KDbField::Byte || type > KDbField::LastType) {
1990 *thisOk = false;
1991 return QVariant();
1992 }
1993 if (!data) { // NULL value
1994 *thisOk = true;
1995 return QVariant();
1996 }
1997 // from most to least frequently used types:
1998
1999 if (KDbField::isTextType(type)) {
2000 *thisOk = true;
2001 //! @todo use KDbDriverBehavior::TEXT_TYPE_MAX_LENGTH for Text type?
2002 return QString::fromUtf8(data, length);
2003 }
2004 if (KDbField::isIntegerType(type)) {
2005 qlonglong minValue, maxValue;
2006 const bool isUnsigned = signedness == KDb::Unsigned;
2007 KDb::getLimitsForFieldType(type, &minValue, &maxValue, signedness);
2008 switch (type) {
2009 case KDbField::Byte: // Byte here too, minValue/maxValue will take care of limits
2011 return isUnsigned ?
2012 convert(&QString::toUShort, data, length, minValue, maxValue, thisOk)
2013 : convert(&QString::toShort, data, length, minValue, maxValue, thisOk);
2014 case KDbField::Integer:
2015 return isUnsigned ?
2016 convert(&QString::toUInt, data, length, minValue, maxValue, thisOk)
2017 : convert(&QString::toInt, data, length, minValue, maxValue, thisOk);
2019 return convert(&QString::toLongLong, data, length, minValue, maxValue, thisOk);
2020 default:
2021 qFatal("Unsupported integer type %d", type);
2022 }
2023 }
2024 if (KDbField::isFPNumericType(type)) {
2025 const QVariant result(QString::fromLatin1(data, length).toDouble(thisOk));
2026 return KDb::iif(*thisOk, result);
2027 }
2028 if (type == KDbField::BLOB) {
2029 *thisOk = length >= 0;
2030 return *thisOk ? QVariant(QByteArray(data, length)) : QVariant();
2031 }
2032 // the default
2033//! @todo date/time?
2034 QVariant result(QString::fromUtf8(data, length));
2035 if (!result.convert(KDbField::variantType(type))) {
2036 *thisOk = false;
2037 return QVariant();
2038 }
2039 *thisOk = true;
2040 return result;
2041}
2042
2044{
2045 QStringList result;
2046 foreach (const QString& path, qApp->libraryPaths()) {
2047 const QString dir(path + QLatin1Char('/') + QLatin1String(KDB_BASE_NAME_LOWER));
2048 if (QDir(dir).exists() && QDir(dir).isReadable()) {
2049 result += dir;
2050 }
2051 }
2052 return result;
2053}
2054
2056{
2057 if (!conn) {
2058 return QString();
2059 }
2060 while (true) {
2061 QString name = QLatin1String("tmp__") + baseName;
2062 for (int i = 0; i < 10; ++i) {
2063 name += QString::number(int(double(qrand()) / RAND_MAX * 0x10), 16);
2064 }
2065 const tristate res = conn->containsTable(name);
2066 if (~res) {
2067 return QString();
2068 } else if (res == false) {
2069 return name;
2070 }
2071 }
2072}
2073
2075{
2076 QString path = KDbUtils::findExe(QLatin1String("sqlite3"));
2077 if (path.isEmpty()) {
2078 kdbWarning() << "Could not find program \"sqlite3\"";
2079 }
2080 return path;
2081}
2082
2083bool KDb::importSqliteFile(const QString &inputFileName, const QString &outputFileName)
2084{
2085 const QString sqlite_app = KDb::sqlite3ProgramPath();
2086 if (sqlite_app.isEmpty()) {
2087 return false;
2088 }
2089
2090 QFileInfo fi(inputFileName);
2091 if (!fi.isReadable()) {
2092 kdbWarning() << "No readable input file" << fi.absoluteFilePath();
2093 return false;
2094 }
2095 QFileInfo fo(outputFileName);
2096 if (QFile(fo.absoluteFilePath()).exists()) {
2097 if (!QFile::remove(fo.absoluteFilePath())) {
2098 kdbWarning() << "Could not remove output file" << fo.absoluteFilePath();
2099 return false;
2100 }
2101 }
2102 kdbDebug() << inputFileName << fi.absoluteDir().path() << fo.absoluteFilePath();
2103
2104 QProcess p;
2105 p.start(sqlite_app, QStringList() << fo.absoluteFilePath());
2106 if (!p.waitForStarted()) {
2107 kdbWarning() << "Failed to start program" << sqlite_app;
2108 return false;
2109 }
2110 QByteArray line(".read " + QFile::encodeName(fi.absoluteFilePath()));
2111 if (p.write(line) != line.length() || !p.waitForBytesWritten()) {
2112 kdbWarning() << "Failed to send \".read\" command to program" << sqlite_app;
2113 return false;
2114 }
2116 if (!p.waitForFinished()) {
2117 kdbWarning() << "Failed to finish program" << sqlite_app;
2118 return false;
2119 }
2120 return true;
2121}
2122
2123//---------
2124
2125bool KDb::isIdentifier(const QString& s)
2126{
2127 int i;
2128 const int sLength = s.length();
2129 for (i = 0; i < sLength; i++) {
2130 const char c = s.at(i).toLower().toLatin1();
2131 if (c == 0 || !(c == '_' || (c >= 'a' && c <= 'z') || (i > 0 && c >= '0' && c <= '9')))
2132 break;
2133 }
2134 return i > 0 && i == sLength;
2135}
2136
2137bool KDb::isIdentifier(const QByteArray& s)
2138{
2139 int i;
2140 const int sLength = s.length();
2141 for (i = 0; i < sLength; i++) {
2142 const char c = s.at(i);
2143 if (c == 0 || !(c == '_' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (i > 0 && c >= '0' && c <= '9'))) {
2144 break;
2145 }
2146 }
2147 return i > 0 && i == sLength;
2148}
2149
2150static inline QString charToIdentifier(const QChar& c)
2151{
2152 if (c.unicode() >= TRANSLITERATION_TABLE_SIZE)
2153 return QLatin1String("_");
2154 const char *const s = transliteration_table[c.unicode()];
2155 return s ? QString::fromLatin1(s) : QLatin1String("_");
2156}
2157
2159{
2160 if (s.isEmpty())
2161 return QString();
2162 QString r, id = s.simplified();
2163 if (id.isEmpty())
2164 return QString();
2165 r.reserve(id.length());
2166 id.replace(QLatin1Char(' '), QLatin1String("_"));
2167 const QChar c = id[0];
2168 const char ch = c.toLatin1();
2169 QString add;
2170 bool wasUnderscore = false;
2171
2172 if (ch >= '0' && ch <= '9') {
2173 r += QLatin1Char('_') + c;
2174 } else {
2175 add = charToIdentifier(c);
2176 r += add;
2177 wasUnderscore = add == QLatin1String("_");
2178 }
2179
2180 const int idLength = id.length();
2181 for (int i = 1; i < idLength; i++) {
2182 add = charToIdentifier(id.at(i));
2183 if (wasUnderscore && add == QLatin1String("_"))
2184 continue;
2185 wasUnderscore = add == QLatin1String("_");
2186 r += add;
2187 }
2188 return r;
2189}
2190
2191QString KDb::identifierExpectedMessage(const QString &valueName, const QVariant& v)
2192{
2193 return QLatin1String("<p>") + kdb::tr("Value of \"%1\" field must be an identifier.")
2194 .arg(valueName)
2195 + QLatin1String("</p><p>")
2196 + kdb::tr("\"%1\" is not a valid identifier.").arg(v.toString()) + QLatin1String("</p>");
2197}
2198
2199//---------
2200
2202{
2203 return valueToSqlInternal(nullptr, ftype, v);
2204}
2205
2206static QByteArray dateToSqlInternal(const QVariant& v, bool allowInvalidKDbDate)
2207{
2208 QByteArray result(QByteArrayLiteral("<INVALID_DATE>"));
2209 if (v.canConvert<KDbDate>()) {
2210 const KDbDate date(v.value<KDbDate>());
2211 if (date.isValid() || allowInvalidKDbDate) {
2212 result = date.toString(); // OK even if invalid or null
2213 }
2214 } else if (v.canConvert<QDate>()) {
2215 const QDate date(v.toDate());
2216 if (date.isValid()) {
2217 result = date.toString(Qt::ISODate).toLatin1();
2218 }
2219 }
2220 return result;
2221}
2222
2224{
2225 return KDbEscapedString('#') + dateToSqlInternal(v, true) + '#';
2226}
2227
2228static QByteArray timeToSqlInternal(const QVariant& v, bool allowInvalidKDbTime)
2229{
2230 QByteArray result(QByteArrayLiteral("<INVALID_TIME>"));
2231 if (v.canConvert<KDbTime>()) {
2232 const KDbTime time(v.value<KDbTime>());
2233 if (time.isValid() || allowInvalidKDbTime) {
2234 result = time.toString(); // OK even if invalid or null
2235 }
2236 } else if (v.canConvert<QTime>()) {
2237 const QTime time(v.toTime());
2238 if (time.isValid()) {
2239 if (time.msec() == 0) {
2240 result = time.toString(Qt::ISODate).toLatin1();
2241 } else {
2242 result = KDbUtils::toISODateStringWithMs(time).toLatin1();
2243 }
2244 }
2245 }
2246 return result;
2247}
2248
2250{
2251 return KDbEscapedString('#') + timeToSqlInternal(v, true) + '#';
2252}
2253
2254static QByteArray dateTimeToSqlInternal(const QVariant& v, char separator, bool allowInvalidKDbDateTime)
2255{
2256 QByteArray result(QByteArrayLiteral("<INVALID_DATETIME>"));
2257 if (v.canConvert<KDbDateTime>()) {
2258 const KDbDateTime dateTime(v.value<KDbDateTime>());
2259 if (dateTime.isValid() || allowInvalidKDbDateTime) {
2260 result = dateTime.toString(); // OK even if invalid or null
2261 }
2262 } else if (v.canConvert<QDateTime>()) {
2263 const QDateTime dateTime(v.toDateTime());
2264 if (dateTime.isValid()) {
2265 result = dateTime.date().toString(Qt::ISODate).toLatin1() + separator;
2266 const QTime time(dateTime.time());
2267 if (time.msec() == 0) {
2268 result += time.toString(Qt::ISODate).toLatin1();
2269 } else {
2270 result += KDbUtils::toISODateStringWithMs(time).toLatin1();
2271 }
2272 }
2273 }
2274 return result;
2275}
2276
2278{
2279 return KDbEscapedString('#') + dateTimeToSqlInternal(v, ' ', true) + '#';
2280}
2281
2283{
2284 return KDb::dateTimeToIsoString(v);
2285}
2286
2288{
2289 return KDbEscapedString('\'') + dateToSqlInternal(v, false) + KDbEscapedString('\'');
2290}
2291
2293{
2294 return KDbEscapedString('\'') + timeToSqlInternal(v, false) + KDbEscapedString('\'');
2295}
2296
2298{
2299 return KDbEscapedString('\'') + dateTimeToSqlInternal(v, 'T', false) + KDbEscapedString('\'');
2300}
2301
2302//--------------------------------------------------------------------------------
2303
2304#ifdef KDB_DEBUG_GUI
2305
2306static KDb::DebugGUIHandler s_debugGUIHandler = nullptr;
2307
2308void KDb::setDebugGUIHandler(KDb::DebugGUIHandler handler)
2309{
2310 s_debugGUIHandler = handler;
2311}
2312
2313void KDb::debugGUI(const QString& text)
2314{
2315 if (s_debugGUIHandler)
2316 s_debugGUIHandler(text);
2317}
2318
2319static KDb::AlterTableActionDebugGUIHandler s_alterTableActionDebugHandler = nullptr;
2320
2321void KDb::setAlterTableActionDebugHandler(KDb::AlterTableActionDebugGUIHandler handler)
2322{
2323 s_alterTableActionDebugHandler = handler;
2324}
2325
2326void KDb::alterTableActionDebugGUI(const QString& text, int nestingLevel)
2327{
2328 if (s_alterTableActionDebugHandler)
2329 s_alterTableActionDebugHandler(text, nestingLevel);
2330}
2331
2332#endif // KDB_DEBUG_GUI
2333
2334#include "KDb.moc"
Database specific connection data, e.g. host, port.
QString toUserVisibleString(UserVisibleStringOptions options=UserVisibleStringOption::AddUser) const
Provides database connection, allowing queries and data modification.
virtual KDbEscapedString escapeString(const QString &str) const
bool executeSql(const KDbEscapedString &sql)
Executes a new native (raw, backend-specific) SQL query.
virtual QString escapeIdentifier(const QString &id) const
Identifier escaping function in the associated KDbDriver.
KDbDriver * driver() const
tristate querySingleRecord(const KDbEscapedString &sql, KDbRecordData *data, QueryRecordOptions options=QueryRecordOption::Default)
tristate querySingleNumber(const KDbEscapedString &sql, int *number, int column=0, QueryRecordOptions options=QueryRecordOption::Default)
tristate containsTable(const QString &tableName)
Generic date/time constant.
Generic date constant.
Detailed definition of driver's default behavior.
bool ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE
A driver manager for finding and loading driver plugins.
KDbResult result() const
KDbResultable * resultable() const
KDbDriver * driver(const QString &id)
Database driver's abstraction.
Definition KDbDriver.h:50
KDbConnection * createConnection(const KDbConnectionData &connData, const KDbConnectionOptions &options)
virtual KDbEscapedString escapeString(const QString &str) const =0
virtual KDbEscapedString valueToSql(KDbField::Type ftype, const QVariant &v) const
Specialized string for escaping.
Meta-data for a field.
Definition KDbField.h:72
MaxLengthStrategy maxLengthStrategy() const
Definition KDbField.cpp:655
void setDescription(const QString &description)
Definition KDbField.cpp:336
int precision() const
Definition KDbField.cpp:286
bool isNumericType() const
Definition KDbField.h:317
KDbTableSchema * table()
Definition KDbField.cpp:585
bool isAutoIncrementAllowed() const
Definition KDbField.h:531
bool isTextType() const
Definition KDbField.h:353
void setType(Type t)
Definition KDbField.cpp:620
void setOptions(Options options)
Definition KDbField.cpp:266
QString name() const
Definition KDbField.cpp:256
QVariant::Type variantType() const
Converts field's type to QVariant equivalent as accurate as possible.
Definition KDbField.h:368
void setCaption(const QString &caption)
Definition KDbField.cpp:321
int maxLength() const
Definition KDbField.cpp:665
@ DefaultMaxLength
Default maximum text length defined globally by the application.
Definition KDbField.h:430
QString caption() const
Definition KDbField.cpp:316
QString description() const
Definition KDbField.cpp:331
void setDefaultValue(const QVariant &def)
Definition KDbField.cpp:711
QString typeName() const
Definition KDbField.h:377
void setMaxLengthStrategy(MaxLengthStrategy strategy)
Definition KDbField.cpp:660
@ Integer
Definition KDbField.h:90
@ Boolean
Definition KDbField.h:92
@ ShortInteger
Definition KDbField.h:89
@ InvalidType
Definition KDbField.h:86
@ BigInteger
Definition KDbField.h:91
@ LongText
Definition KDbField.h:99
void setPrecision(int p)
Definition KDbField.cpp:676
bool isFPNumericType() const
Definition KDbField.h:335
@ NotEmpty
only legal for string-like and blob fields
Definition KDbField.h:139
void setVisibleDecimalPlaces(int p)
Definition KDbField.cpp:692
Type type() const
Definition KDbField.cpp:379
void setName(const QString &name)
Definition KDbField.cpp:615
Constraints constraints() const
Definition KDbField.cpp:301
void setMaxLength(int maxLength)
Definition KDbField.cpp:670
bool isIntegerType() const
Definition KDbField.h:326
QString typeString() const
Definition KDbField.h:393
bool isUniqueKey() const
Definition KDbField.h:292
QString typeGroupName() const
Definition KDbField.h:387
QVariant defaultValue() const
Definition KDbField.cpp:281
void setCustomProperty(const QByteArray &propertyName, const QVariant &value)
Sets value value for custom property propertyName.
Options options() const
Definition KDbField.cpp:261
void setConstraints(Constraints c)
Definition KDbField.cpp:630
TypeGroup typeGroup() const
Definition KDbField.h:382
Record source information that can be specified for the lookup field schema.
Provides information about lookup field's setup.
bool setProperties(const QMap< QByteArray, QVariant > &values)
QList< int > visibleColumns() const
QList< int > columnWidths() const
KDbLookupFieldSchemaRecordSource recordSource() const
bool setProperty(const QByteArray &propertyName, const QVariant &value)
DisplayWidget displayWidget() const
MessageType
Message types.
virtual void showErrorMessage(KDbMessageHandler::MessageType messageType, const QString &message, const QString &details=QString(), const QString &caption=QString())=0
Structure for storing single record with type information.
QString description
Detailed error description, empty by default.
Definition KDbError.h:129
QString message
Error message, empty by default.
Definition KDbError.h:128
virtual KDbEscapedString recentSqlString() const
QString messageTitle
QString serverMessage
bool isError() const
Definition KDbResult.cpp:64
QString message
Interface for classes providing a result.
virtual QString serverResultName() const
KDbLookupFieldSchema * lookupFieldSchema(const KDbField &field)
bool setLookupFieldSchema(const QString &fieldName, KDbLookupFieldSchema *lookupFieldSchema)
Generic time constant.
3-state logical type with three values: true, false and cancelled and convenient operators.
Type type(const QSqlDatabase &db)
A database connectivity and creation framework.
KDB_EXPORT KDbEscapedString timeToSql(const QVariant &v)
Converts time value to its string representation required by KDBSQL commands.
KDB_EXPORT KDbEscapedString dateToSql(const QVariant &v)
Converts date value to its string representation required by KDBSQL commands.
@ Hex
Escaping like 1FAD without quotes or prefixes.
KDB_EXPORT QString sqlite3ProgramPath()
KDB_EXPORT QString escapeIdentifierAndAddQuotes(const QString &string)
Definition KDb.cpp:1346
T iif(bool ok, const T &value)
Definition KDb.h:841
KDB_EXPORT QDomElement saveNumberElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString &elementName, int value)
Definition KDb.cpp:1169
KDB_EXPORT bool isExtendedTableFieldProperty(const QByteArray &propertyName)
for isExtendedTableProperty()
Definition KDb.cpp:954
KDB_EXPORT QString numberToString(double value, int decimalPlaces)
Definition KDb.cpp:654
KDB_EXPORT void getProperties(const KDbLookupFieldSchema *lookup, QMap< QByteArray, QVariant > *values)
Definition KDb.cpp:758
KDB_EXPORT QString escapeBLOB(const QByteArray &array, BLOBEscapingType type)
KDB_EXPORT bool setFieldProperties(KDbField *field, const QMap< QByteArray, QVariant > &values)
Definition KDb.cpp:833
SplitToTableAndFieldPartsOptions
Used in splitToTableAndFieldParts().
Definition KDb.h:246
@ SetFieldNameIfNoTableName
see splitToTableAndFieldParts()
Definition KDb.h:248
KDB_EXPORT bool isIdentifier(const QString &s)
KDB_EXPORT QByteArray pgsqlByteaToByteArray(const char *data, int length=-1)
KDB_EXPORT QString escapeIdentifier(const QString &string)
Definition KDb.cpp:1334
KDB_EXPORT KDbEscapedString timeToIsoString(const QVariant &v)
Converts time value to its string representation in ISO 8601 Time format.
KDB_EXPORT quint64 lastInsertedAutoIncValue(QSharedPointer< KDbSqlResult > result, const QString &autoIncrementFieldName, const QString &tableName, quint64 *recordId=nullptr)
Returns value of last inserted record for an autoincrement field.
Definition KDb.cpp:392
KDB_EXPORT bool deleteRecords(KDbConnection *conn, const QString &tableName, const QString &keyname, KDbField::Type keytype, const QVariant &keyval)
Deletes records using one generic criteria.
Definition KDb.cpp:342
KDB_EXPORT QString defaultFileBasedDriverMimeType()
KDB_EXPORT KDbEscapedString sqlWhere(KDbDriver *drv, KDbField::Type t, const QString &fieldName, const QVariant &value)
Definition KDb.cpp:440
KDB_EXPORT KDbField::Type maximumForIntegerFieldTypes(KDbField::Type t1, KDbField::Type t2)
KDB_EXPORT QStringList deserializeList(const QString &data)
KDB_EXPORT bool isEmptyValue(KDbField::Type type, const QVariant &value)
Definition KDb.cpp:429
KDB_EXPORT QStringList fieldTypeStringsForGroup(KDbField::TypeGroup typeGroup)
Definition KDb.cpp:495
KDB_EXPORT QString defaultFileBasedDriverId()
KDB_EXPORT bool isLookupFieldSchemaProperty(const QByteArray &propertyName)
for isLookupFieldSchemaProperty()
Definition KDb.cpp:976
KDB_EXPORT KDbEscapedString dateToIsoString(const QVariant &v)
Converts date value to its string representation in ISO 8601 DateTime format.
KDB_EXPORT void getFieldProperties(const KDbField &field, QMap< QByteArray, QVariant > *values)
Definition KDb.cpp:787
KDB_EXPORT bool importSqliteFile(const QString &inputFileName, const QString &outputFileName)
KDB_EXPORT QString escapeString(const QString &string)
Definition KDb.cpp:1356
KDB_EXPORT KDbVersionInfo version()
Definition KDb.cpp:336
KDB_EXPORT QString variantToString(const QVariant &v)
KDB_EXPORT bool isKDbSqlKeyword(const QByteArray &word)
KDB_EXPORT KDbField::Type defaultFieldTypeForGroup(KDbField::TypeGroup typeGroup)
Definition KDb.cpp:500
KDB_EXPORT QList< int > stringListToIntList(const QStringList &list, bool *ok=nullptr)
KDB_EXPORT bool supportsVisibleDecimalPlacesProperty(KDbField::Type type)
Definition KDb.cpp:623
KDB_EXPORT QStringList fieldTypeNamesForGroup(KDbField::TypeGroup typeGroup)
Definition KDb.cpp:490
KDB_EXPORT QString identifierExpectedMessage(const QString &valueName, const QVariant &v)
KDB_EXPORT KDbEscapedString dateTimeToIsoString(const QVariant &v)
Converts date/time value to its string representation in ISO 8601 DateTime format - with "T" delimite...
KDB_EXPORT QString stringToIdentifier(const QString &s)
KDB_EXPORT QString loadStringPropertyValueFromDom(const QDomNode &node, bool *ok)
Definition KDb.cpp:1110
KDB_EXPORT QString simplifiedFieldTypeName(KDbField::Type type)
KDB_EXPORT KDbEscapedString valueToSql(KDbField::Type ftype, const QVariant &v)
KDB_EXPORT bool setFieldProperty(KDbField *field, const QByteArray &propertyName, const QVariant &value)
Definition KDb.cpp:981
KDB_EXPORT QDomElement saveBooleanElementToDom(QDomDocument *doc, QDomElement *parentEl, const QString &elementName, bool value)
Definition KDb.cpp:1183
KDB_EXPORT KDbField::Type intToFieldType(int type)
Definition KDb.cpp:670
KDB_EXPORT QVariant emptyValueForFieldType(KDbField::Type type)
Used in KDb::emptyValueForFieldType()
Definition KDb.cpp:1222
KDB_EXPORT int loadIntPropertyValueFromDom(const QDomNode &node, bool *ok)
Definition KDb.cpp:1097
KDB_EXPORT QString serializeList(const QStringList &list)
KDB_EXPORT tristate showConnectionTestDialog(QWidget *parent, const KDbConnectionData &data, KDbMessageHandler *msgHandler)
Shows connection test dialog.
Definition KDb.cpp:592
KDB_EXPORT bool deleteAllRecords(KDbConnection *conn, const QString &tableName)
Deletes all records from table tableName.
Definition KDb.cpp:384
Signedness
A property of numeric values.
Definition KDbGlobal.h:151
@ Signed
Values can be both positive and negative.
Definition KDbGlobal.h:152
@ Unsigned
Values can be both non-negative.
Definition KDbGlobal.h:153
KDB_EXPORT tristate idForObjectName(KDbConnection *conn, int *id, const QString &objName, int objType)
Finds an identifier for object objName of type objType.
Definition KDb.cpp:579
KDB_EXPORT void getLimitsForFieldType(KDbField::Type type, qlonglong *minValue, qlonglong *maxValue, KDb::Signedness signedness=KDb::Signed)
Provides limits for values of type type.
KDB_EXPORT QString unescapeString(const QString &string, char quote, int *errorPosition=nullptr)
Unescapes characters in string string for the KDBSQL dialect.
KDB_EXPORT QVariant cstringToVariant(const char *data, KDbField::Type type, bool *ok, int length=-1, KDb::Signedness signedness=KDb::Signed)
KDB_EXPORT QStringList libraryPaths()
KDB_EXPORT QString numberToLocaleString(double value, int decimalPlaces)
Returns number converted to string using default locale.
Definition KDb.cpp:659
KDB_EXPORT bool splitToTableAndFieldParts(const QString &string, QString *tableName, QString *fieldName, SplitToTableAndFieldPartsOptions option=FailIfNoTableOrFieldName)
Definition KDb.cpp:603
KDB_EXPORT KDbField::TypeGroup intToFieldTypeGroup(int typeGroup)
Definition KDb.cpp:678
KDB_EXPORT QByteArray xHexToByteArray(const char *data, int length=-1, bool *ok=nullptr)
KDB_EXPORT QVariant loadPropertyValueFromDom(const QDomNode &node, bool *ok)
Definition KDb.cpp:1123
KDB_EXPORT QByteArray zeroXHexToByteArray(const char *data, int length=-1, bool *ok=nullptr)
KDB_EXPORT void getHTMLErrorMesage(const KDbResultable &resultable, QString *msg, QString *details)
Sets HTML-formatted error message with extra details obtained from result object.
Definition KDb.cpp:505
KDB_EXPORT QVariant stringToVariant(const QString &s, QVariant::Type type, bool *ok)
KDB_EXPORT QString temporaryTableName(KDbConnection *conn, const QString &baseName)
KDB_EXPORT bool isDefaultValueAllowed(const KDbField &field)
KDB_DEPRECATED_EXPORT KDbEscapedString dateTimeToSql(const QDateTime &v)
Converts date/time value to its string representation in ISO 8601 DateTime format - with "T" delimite...
KDB_EXPORT QList< int > deserializeIntList(const QString &data, bool *ok)
T convert(const QVariant &value)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KIOCORE_EXPORT void add(const QString &fileClass, const QString &directory)
QString name(StandardAction id)
const QList< QKeySequence > & end()
int decimalPlaces(const int rangeMax, const int significantFigures)
char at(qsizetype i) const const
void clear()
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
void resize(qsizetype newSize, char c)
qsizetype size() const const
QChar fromLatin1(char c)
char toLatin1() const const
char32_t toLower(char32_t ucs4)
char16_t & unicode()
QDate currentDate()
QDateTime currentDateTime()
virtual void accept()
virtual int exec()
virtual void reject()
QDomElement createElement(const QString &tagName)
QDomText createTextNode(const QString &value)
QString text() const const
QDomNode appendChild(const QDomNode &newChild)
QString nodeName() const const
QDomElement toElement() const const
QByteArray encodeName(const QString &fileName)
bool exists(const QString &fileName)
bool remove()
bool testFlag(Enum flag) const const
bool contains(const Key &key) const const
T value(const Key &key) const const
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
T & first()
bool isEmpty() const const
T value(qsizetype i) const const
QString toString(QDate date, FormatType format) const const
ConstIterator
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
Q_SLOTSQ_SLOTS
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void closeWriteChannel()
void start(OpenMode mode)
virtual bool waitForBytesWritten(int msecs) override
bool waitForFinished(int msecs)
bool waitForStarted(int msecs)
void setValue(int progress)
void reset(T *other)
iterator insert(const T &value)
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
qsizetype capacity() const const
void clear()
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
QString simplified() const const
qsizetype size() const const
void squeeze()
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
qlonglong toLongLong(bool *ok, int base) const const
uint toUInt(bool *ok, int base) const const
ushort toUShort(bool *ok, int base) const const
void truncate(qsizetype position)
CaseInsensitive
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTime currentTime()
void start()
void stop()
Type type() const const
bool canConvert() const const
bool isNull() const const
bool toBool() const const
QByteArray toByteArray() const const
QDate toDate() const const
QDateTime toDateTime() const const
int toInt(bool *ok) const const
QString toString() const const
QStringList toStringList() const const
QTime toTime() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:19:07 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.