1 /* This file is part of the KDE project
2    Copyright (C) 2003-2017 Jarosław Staniek <staniek@kde.org>
3 
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this program; see the file COPYING.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18 */
19 
20 #include "KDbConnection.h"
21 #include "KDbConnection_p.h"
22 #include "KDbCursor.h"
23 #include "KDbDriverBehavior.h"
24 #include "KDbDriverMetaData.h"
25 #include "KDbDriver_p.h"
26 #include "KDbLookupFieldSchema.h"
27 #include "KDbNativeStatementBuilder.h"
28 #include "KDbQuerySchema.h"
29 #include "KDbQuerySchema_p.h"
30 #include "KDbRecordData.h"
31 #include "KDbRecordEditBuffer.h"
32 #include "KDbRelationship.h"
33 #include "KDbSqlRecord.h"
34 #include "KDbSqlResult.h"
35 #include "KDbTableOrQuerySchema.h"
36 #include "KDbTableSchemaChangeListener.h"
37 #include "KDbTransactionData.h"
38 #include "KDbTransactionGuard.h"
39 #include "kdb_debug.h"
40 
41 #include <QDir>
42 #include <QFileInfo>
43 #include <QDomDocument>
44 
45 /*! Version number of extended table schema.
46 
47   List of changes:
48   * 2: (Kexi 2.5.0) Added maxLengthIsDefault property (type: bool, if true, KDbField::maxLengthStrategy() == KDbField::DefaultMaxLength)
49   * 1: (Kexi 1.x) Initial version
50 */
51 #define KDB_EXTENDED_TABLE_SCHEMA_VERSION 2
52 
KDbConnectionInternal(KDbConnection * conn)53 KDbConnectionInternal::KDbConnectionInternal(KDbConnection *conn)
54         : connection(conn)
55 {
56 }
57 
58 class CursorDeleter
59 {
60 public:
CursorDeleter(KDbCursor * cursor)61     explicit CursorDeleter(KDbCursor *cursor) {
62         delete cursor;
63     }
64 };
65 
66 //================================================
67 
68 class Q_DECL_HIDDEN KDbConnectionOptions::Private
69 {
70 public:
Private()71     Private() : connection(nullptr) {}
Private(const Private & other)72     Private(const Private &other) {
73         copy(other);
74     }
75 #define KDbConnectionOptionsPrivateArgs(o) std::tie(o.connection)
copy(const Private & other)76     void copy(const Private &other) {
77         KDbConnectionOptionsPrivateArgs((*this)) = KDbConnectionOptionsPrivateArgs(other);
78     }
operator ==(const Private & other) const79     bool operator==(const Private &other) const {
80         return KDbConnectionOptionsPrivateArgs((*this)) == KDbConnectionOptionsPrivateArgs(other);
81     }
82     KDbConnection *connection;
83 };
84 
KDbConnectionOptions()85 KDbConnectionOptions::KDbConnectionOptions()
86  : d(new Private)
87 {
88     KDbUtils::PropertySet::insert("readOnly", false, tr("Read only", "Read only connection"));
89 }
90 
KDbConnectionOptions(const KDbConnectionOptions & other)91 KDbConnectionOptions::KDbConnectionOptions(const KDbConnectionOptions &other)
92  : KDbUtils::PropertySet(other)
93  , d(new Private(*other.d))
94 {
95 }
96 
~KDbConnectionOptions()97 KDbConnectionOptions::~KDbConnectionOptions()
98 {
99     delete d;
100 }
101 
operator =(const KDbConnectionOptions & other)102 KDbConnectionOptions& KDbConnectionOptions::operator=(const KDbConnectionOptions &other)
103 {
104     if (this != &other) {
105         KDbUtils::PropertySet::operator=(other);
106         d->copy(*other.d);
107     }
108     return *this;
109 }
110 
operator ==(const KDbConnectionOptions & other) const111 bool KDbConnectionOptions::operator==(const KDbConnectionOptions &other) const
112 {
113     return KDbUtils::PropertySet::operator==(other) && *d == *other.d;
114 }
115 
isReadOnly() const116 bool KDbConnectionOptions::isReadOnly() const
117 {
118     return property("readOnly").value().toBool();
119 }
120 
insert(const QByteArray & name,const QVariant & value,const QString & caption)121 void KDbConnectionOptions::insert(const QByteArray &name, const QVariant &value,
122                                   const QString &caption)
123 {
124     if (name == "readOnly") {
125         setReadOnly(value.toBool());
126         return;
127     }
128     QString realCaption;
129     if (property(name).caption().isEmpty()) { // don't allow to change the caption
130         realCaption = caption;
131     }
132     KDbUtils::PropertySet::insert(name, value, realCaption);
133 }
134 
setCaption(const QByteArray & name,const QString & caption)135 void KDbConnectionOptions::setCaption(const QByteArray &name, const QString &caption)
136 {
137     if (name == "readOnly") {
138         return;
139     }
140     KDbUtils::PropertySet::setCaption(name, caption);
141 }
142 
setValue(const QByteArray & name,const QVariant & value)143 void KDbConnectionOptions::setValue(const QByteArray &name, const QVariant &value)
144 {
145     if (name == "readOnly") {
146         setReadOnly(value.toBool());
147         return;
148     }
149     KDbUtils::PropertySet::setValue(name, value);
150 }
151 
remove(const QByteArray & name)152 void KDbConnectionOptions::remove(const QByteArray &name)
153 {
154     if (name == "readOnly") {
155         return;
156     }
157     KDbUtils::PropertySet::remove(name);
158 }
159 
setReadOnly(bool set)160 void KDbConnectionOptions::setReadOnly(bool set)
161 {
162     if (d->connection && d->connection->isConnected()) {
163         return; //sanity
164     }
165     KDbUtils::PropertySet::setValue("readOnly", set);
166 }
167 
setConnection(KDbConnection * connection)168 void KDbConnectionOptions::setConnection(KDbConnection *connection)
169 {
170     d->connection = connection;
171 }
172 
173 //================================================
174 
KDbConnectionPrivate(KDbConnection * const conn,KDbDriver * drv,const KDbConnectionData & _connData,const KDbConnectionOptions & _options)175 KDbConnectionPrivate::KDbConnectionPrivate(KDbConnection* const conn, KDbDriver *drv, const KDbConnectionData& _connData,
176                   const KDbConnectionOptions &_options)
177         : conn(conn)
178         , connData(_connData)
179         , options(_options)
180         , driver(drv)
181         , dbProperties(conn)
182 {
183     options.setConnection(conn);
184 }
185 
~KDbConnectionPrivate()186 KDbConnectionPrivate::~KDbConnectionPrivate()
187 {
188     options.setConnection(nullptr);
189     deleteAllCursors();
190     delete m_parser;
191     qDeleteAll(tableSchemaChangeListeners);
192     qDeleteAll(obsoleteQueries);
193 }
194 
deleteAllCursors()195 void KDbConnectionPrivate::deleteAllCursors()
196 {
197     QSet<KDbCursor*> cursorsToDelete(cursors);
198     cursors.clear();
199     for(KDbCursor* c : cursorsToDelete) {
200         CursorDeleter deleter(c);
201     }
202 }
203 
errorInvalidDBContents(const QString & details)204 void KDbConnectionPrivate::errorInvalidDBContents(const QString& details)
205 {
206     conn->m_result = KDbResult(ERR_INVALID_DATABASE_CONTENTS,
207                                KDbConnection::tr("Invalid database contents. %1").arg(details));
208 }
209 
strItIsASystemObject() const210 QString KDbConnectionPrivate::strItIsASystemObject() const
211 {
212     return KDbConnection::tr("It is a system object.");
213 }
214 
setupKDbSystemSchema()215 void KDbConnectionPrivate::setupKDbSystemSchema()
216 {
217     if (!m_internalKDbTables.isEmpty()) {
218         return; //already set up
219     }
220     {
221         KDbInternalTableSchema *t_objects = new KDbInternalTableSchema(QLatin1String("kexi__objects"));
222         t_objects->addField(new KDbField(QLatin1String("o_id"),
223                                       KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned));
224         t_objects->addField(new KDbField(QLatin1String("o_type"), KDbField::Byte, nullptr, KDbField::Unsigned));
225         t_objects->addField(new KDbField(QLatin1String("o_name"), KDbField::Text));
226         t_objects->addField(new KDbField(QLatin1String("o_caption"), KDbField::Text));
227         t_objects->addField(new KDbField(QLatin1String("o_desc"), KDbField::LongText));
228         //kdbDebug() << *t_objects;
229         insertTable(t_objects);
230     }
231     {
232         KDbInternalTableSchema *t_objectdata = new KDbInternalTableSchema(QLatin1String("kexi__objectdata"));
233         t_objectdata->addField(new KDbField(QLatin1String("o_id"),
234                                          KDbField::Integer, KDbField::NotNull, KDbField::Unsigned));
235         t_objectdata->addField(new KDbField(QLatin1String("o_data"), KDbField::LongText));
236         t_objectdata->addField(new KDbField(QLatin1String("o_sub_id"), KDbField::Text));
237         insertTable(t_objectdata);
238     }
239     {
240         KDbInternalTableSchema *t_fields = new KDbInternalTableSchema(QLatin1String("kexi__fields"));
241         t_fields->addField(new KDbField(QLatin1String("t_id"), KDbField::Integer, nullptr, KDbField::Unsigned));
242         t_fields->addField(new KDbField(QLatin1String("f_type"), KDbField::Byte, nullptr, KDbField::Unsigned));
243         t_fields->addField(new KDbField(QLatin1String("f_name"), KDbField::Text));
244         t_fields->addField(new KDbField(QLatin1String("f_length"), KDbField::Integer));
245         t_fields->addField(new KDbField(QLatin1String("f_precision"), KDbField::Integer));
246         t_fields->addField(new KDbField(QLatin1String("f_constraints"), KDbField::Integer));
247         t_fields->addField(new KDbField(QLatin1String("f_options"), KDbField::Integer));
248         t_fields->addField(new KDbField(QLatin1String("f_default"), KDbField::Text));
249         //these are additional properties:
250         t_fields->addField(new KDbField(QLatin1String("f_order"), KDbField::Integer));
251         t_fields->addField(new KDbField(QLatin1String("f_caption"), KDbField::Text));
252         t_fields->addField(new KDbField(QLatin1String("f_help"), KDbField::LongText));
253         insertTable(t_fields);
254     }
255     {
256         KDbInternalTableSchema *t_db = new KDbInternalTableSchema(QLatin1String("kexi__db"));
257         t_db->addField(new KDbField(QLatin1String("db_property"),
258                                  KDbField::Text, KDbField::NoConstraints, KDbField::NoOptions, 32));
259         t_db->addField(new KDbField(QLatin1String("db_value"), KDbField::LongText));
260         insertTable(t_db);
261     }
262 }
263 
insertTable(KDbTableSchema * tableSchema)264 void KDbConnectionPrivate::insertTable(KDbTableSchema* tableSchema)
265 {
266     KDbInternalTableSchema* internalTable = dynamic_cast<KDbInternalTableSchema*>(tableSchema);
267     if (internalTable) {
268         m_internalKDbTables.insert(internalTable);
269     } else {
270         m_tables.insert(tableSchema->id(), tableSchema);
271     }
272     m_tablesByName.insert(tableSchema->name(), tableSchema);
273 }
274 
removeTable(int id)275 void KDbConnectionPrivate::removeTable(int id)
276 {
277     QScopedPointer<KDbTableSchema> toDelete(m_tables.take(id));
278     if (!toDelete) {
279         kdbWarning() << "Could not find table to delete with id=" << id;
280         return;
281     }
282     KDbTableSchemaChangeListener::unregisterForChanges(conn, toDelete.data());
283     const int count = m_tablesByName.remove(toDelete->name());
284     Q_ASSERT_X(count == 1, "KDbConnectionPrivate::removeTable", "Table to remove not found");
285 }
286 
takeTable(KDbTableSchema * tableSchema)287 void KDbConnectionPrivate::takeTable(KDbTableSchema* tableSchema)
288 {
289     if (m_tables.isEmpty()) {
290         return;
291     }
292     m_tables.take(tableSchema->id());
293     m_tablesByName.take(tableSchema->name());
294 }
295 
renameTable(KDbTableSchema * tableSchema,const QString & newName)296 void KDbConnectionPrivate::renameTable(KDbTableSchema* tableSchema, const QString& newName)
297 {
298     m_tablesByName.take(tableSchema->name());
299     tableSchema->setName(newName);
300     m_tablesByName.insert(tableSchema->name(), tableSchema);
301 }
302 
changeTableId(KDbTableSchema * tableSchema,int newId)303 void KDbConnectionPrivate::changeTableId(KDbTableSchema* tableSchema, int newId)
304 {
305     m_tables.take(tableSchema->id());
306     m_tables.insert(newId, tableSchema);
307 }
308 
clearTables()309 void KDbConnectionPrivate::clearTables()
310 {
311     m_tablesByName.clear();
312     qDeleteAll(m_internalKDbTables);
313     m_internalKDbTables.clear();
314     QHash<int, KDbTableSchema*> tablesToDelete(m_tables);
315     m_tables.clear();
316     qDeleteAll(tablesToDelete);
317 }
318 
insertQuery(KDbQuerySchema * query)319 void KDbConnectionPrivate::insertQuery(KDbQuerySchema* query)
320 {
321     m_queries.insert(query->id(), query);
322     m_queriesByName.insert(query->name(), query);
323 }
324 
removeQuery(KDbQuerySchema * querySchema)325 void KDbConnectionPrivate::removeQuery(KDbQuerySchema* querySchema)
326 {
327     m_queriesByName.remove(querySchema->name());
328     m_queries.remove(querySchema->id());
329     delete querySchema;
330 }
331 
setQueryObsolete(KDbQuerySchema * query)332 void KDbConnectionPrivate::setQueryObsolete(KDbQuerySchema* query)
333 {
334     obsoleteQueries.insert(query);
335     m_queriesByName.take(query->name());
336     m_queries.take(query->id());
337 }
338 
clearQueries()339 void KDbConnectionPrivate::clearQueries()
340 {
341     qDeleteAll(m_queries);
342     m_queries.clear();
343 }
344 
setupTableSchema(KDbTableSchema * table)345 KDbTableSchema* KDbConnectionPrivate::setupTableSchema(KDbTableSchema *table)
346 {
347     Q_ASSERT(table);
348     QScopedPointer<KDbTableSchema> newTable(table);
349     KDbCursor *cursor;
350     if (!(cursor = conn->executeQuery(
351             KDbEscapedString("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, "
352                              "f_options, f_default, f_order, f_caption, f_help "
353                              "FROM kexi__fields WHERE t_id=%1 ORDER BY f_order")
354                             .arg(driver->valueToSql(KDbField::Integer, table->id())))))
355     {
356         return nullptr;
357     }
358     if (!cursor->moveFirst()) {
359         if (!cursor->result().isError() && cursor->eof()) {
360             conn->m_result = KDbResult(tr("Table has no fields defined."));
361         }
362         conn->deleteCursor(cursor);
363         return nullptr;
364     }
365 
366     // For each field: load its schema
367     KDbRecordData fieldData;
368     bool ok = true;
369     while (!cursor->eof()) {
370 //  kdbDebug()<<"@@@ f_name=="<<cursor->value(2).asCString();
371         if (!cursor->storeCurrentRecord(&fieldData)) {
372             ok = false;
373             break;
374         }
375         KDbField *f = conn->setupField(fieldData);
376         if (!f || !table->addField(f)) {
377             ok = false;
378             break;
379         }
380         cursor->moveNext();
381     }
382 
383     if (!ok) {//error:
384         conn->deleteCursor(cursor);
385         return nullptr;
386     }
387 
388     if (!conn->deleteCursor(cursor)) {
389         return nullptr;
390     }
391 
392     if (!conn->loadExtendedTableSchemaData(table)) {
393         return nullptr;
394     }
395     //store locally:
396     insertTable(table);
397     return newTable.take();
398 }
399 
setupQuerySchema(KDbQuerySchema * query)400 KDbQuerySchema* KDbConnectionPrivate::setupQuerySchema(KDbQuerySchema *query)
401 {
402     Q_ASSERT(query);
403     QScopedPointer<KDbQuerySchema> newQuery(query);
404     QString sql;
405     if (!conn->loadDataBlock(query->id(), &sql, QLatin1String("sql"))) {
406         conn->m_result = KDbResult(
407             ERR_OBJECT_NOT_FOUND,
408             tr("Could not find definition for query \"%1\". Deleting this query is recommended.")
409                 .arg(query->name()));
410         return nullptr;
411     }
412     const QString queryName(query->name());
413     if (!parser()->parse(KDbEscapedString(sql), query)) {
414         newQuery.take(); // query is destroyed by the parser
415         conn->m_result = KDbResult(
416             ERR_SQL_PARSE_ERROR, tr("<p>Could not load definition for query \"%1\". "
417                                     "SQL statement for this query is invalid:<br><tt>%2</tt></p>\n"
418                                     "<p>This query can be edited only in Text View.</p>")
419                                      .arg(queryName, sql));
420         return nullptr;
421     }
422     insertQuery(query);
423     return newQuery.take();
424 }
425 
fieldsExpanded(const KDbQuerySchema * query)426 KDbQuerySchemaFieldsExpanded *KDbConnectionPrivate::fieldsExpanded(const KDbQuerySchema *query)
427 {
428     return m_fieldsExpandedCache[query];
429 }
430 
insertFieldsExpanded(const KDbQuerySchema * query,KDbQuerySchemaFieldsExpanded * cache)431 void KDbConnectionPrivate::insertFieldsExpanded(const KDbQuerySchema *query, KDbQuerySchemaFieldsExpanded *cache)
432 {
433     m_fieldsExpandedCache.insert(query, cache);
434 }
435 
removeFieldsExpanded(const KDbQuerySchema * query)436 void KDbConnectionPrivate::removeFieldsExpanded(const KDbQuerySchema *query)
437 {
438     //kdbDebug() << "**CACHE REMOVE**" << query;
439     m_fieldsExpandedCache.remove(query);
440 }
441 
442 //================================================
443 
444 namespace {
445 //! @internal static: list of internal KDb system table names
446 class SystemTables : public QStringList
447 {
448 public:
SystemTables()449     SystemTables()
450         : QStringList({
451             QLatin1String("kexi__objects"),
452             QLatin1String("kexi__objectdata"),
453             QLatin1String("kexi__fields"),
454             QLatin1String("kexi__db")})
455     {}
456 };
457 }
458 
Q_GLOBAL_STATIC(SystemTables,g_kdbSystemTableNames)459 Q_GLOBAL_STATIC(SystemTables, g_kdbSystemTableNames)
460 
461 KDbConnection::KDbConnection(KDbDriver *driver, const KDbConnectionData& connData,
462                              const KDbConnectionOptions &options)
463         : d(new KDbConnectionPrivate(this, driver, connData, options))
464 {
465     if (d->connData.driverId().isEmpty()) {
466         d->connData.setDriverId(d->driver->metaData()->id());
467     }
468 }
469 
destroy()470 void KDbConnection::destroy()
471 {
472     disconnect();
473     //do not allow the driver to touch me: I will kill myself.
474     d->driver->d->connections.remove(this);
475 }
476 
~KDbConnection()477 KDbConnection::~KDbConnection()
478 {
479     KDbConnectionPrivate *thisD = d;
480     d = nullptr; // make sure d is nullptr before destructing
481     delete thisD;
482 }
483 
data() const484 KDbConnectionData KDbConnection::data() const
485 {
486     return d->connData;
487 }
488 
driver() const489 KDbDriver* KDbConnection::driver() const
490 {
491     return d->driver;
492 }
493 
connect()494 bool KDbConnection::connect()
495 {
496     clearResult();
497     if (d->isConnected) {
498         m_result = KDbResult(ERR_ALREADY_CONNECTED,
499                              tr("Connection already established."));
500         return false;
501     }
502 
503     d->serverVersion.clear();
504     if (!(d->isConnected = drv_connect())) {
505         if (m_result.code() == ERR_NONE) {
506             m_result.setCode(ERR_OTHER);
507         }
508         m_result.setMessage(d->driver->metaData()->isFileBased() ?
509                     tr("Could not open \"%1\" project file.")
510                        .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName()))
511                  :  tr("Could not connect to \"%1\" database server.")
512                        .arg(d->connData.toUserVisibleString()));
513     }
514     if (d->isConnected && !d->driver->behavior()->USING_DATABASE_REQUIRED_TO_CONNECT) {
515         if (!drv_getServerVersion(&d->serverVersion))
516             return false;
517     }
518     return d->isConnected;
519 }
520 
isDatabaseUsed() const521 bool KDbConnection::isDatabaseUsed() const
522 {
523     return !d->usedDatabase.isEmpty() && d->isConnected && drv_isDatabaseUsed();
524 }
525 
clearResult()526 void KDbConnection::clearResult()
527 {
528     KDbResultable::clearResult();
529 }
530 
disconnect()531 bool KDbConnection::disconnect()
532 {
533     clearResult();
534     if (!d->isConnected)
535         return true;
536 
537     if (!closeDatabase())
538         return false;
539 
540     bool ok = drv_disconnect();
541     if (ok)
542         d->isConnected = false;
543     return ok;
544 }
545 
isConnected() const546 bool KDbConnection::isConnected() const
547 {
548     return d->isConnected;
549 }
550 
checkConnected()551 bool KDbConnection::checkConnected()
552 {
553     if (d->isConnected) {
554         clearResult();
555         return true;
556     }
557     m_result = KDbResult(ERR_NO_CONNECTION,
558                          tr("Not connected to the database server."));
559     return false;
560 }
561 
checkIsDatabaseUsed()562 bool KDbConnection::checkIsDatabaseUsed()
563 {
564     if (isDatabaseUsed()) {
565         clearResult();
566         return true;
567     }
568     m_result = KDbResult(ERR_NO_DB_USED,
569                          tr("Currently no database is used."));
570     return false;
571 }
572 
databaseNames(bool also_system_db)573 QStringList KDbConnection::databaseNames(bool also_system_db)
574 {
575     //kdbDebug() << also_system_db;
576     if (!checkConnected())
577         return QStringList();
578 
579     QString tmpdbName;
580     //some engines need to have opened any database before executing "create database"
581     if (!useTemporaryDatabaseIfNeeded(&tmpdbName))
582         return QStringList();
583 
584     QStringList list;
585     bool ret = drv_getDatabasesList(&list);
586 
587     if (!tmpdbName.isEmpty()) {
588         //whatever result is - now we have to close temporary opened database:
589         if (!closeDatabase())
590             return QStringList();
591     }
592 
593     if (!ret)
594         return QStringList();
595 
596     if (also_system_db)
597         return list;
598     //filter system databases:
599     for (QMutableListIterator<QString> it(list); it.hasNext();) {
600         if (d->driver->isSystemDatabaseName(it.next())) {
601             it.remove();
602         }
603     }
604     return list;
605 }
606 
drv_getDatabasesList(QStringList * list)607 bool KDbConnection::drv_getDatabasesList(QStringList* list)
608 {
609     list->clear();
610     return true;
611 }
612 
drv_databaseExists(const QString & dbName,bool ignoreErrors)613 bool KDbConnection::drv_databaseExists(const QString &dbName, bool ignoreErrors)
614 {
615     QStringList list = databaseNames(true);//also system
616     if (m_result.isError()) {
617         return false;
618     }
619 
620     if (list.indexOf(dbName) == -1) {
621         if (!ignoreErrors)
622             m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
623                                  tr("The database \"%1\" does not exist.").arg(dbName));
624         return false;
625     }
626 
627     return true;
628 }
629 
databaseExists(const QString & dbName,bool ignoreErrors)630 bool KDbConnection::databaseExists(const QString &dbName, bool ignoreErrors)
631 {
632 // kdbDebug() << dbName << ignoreErrors;
633     if (d->driver->behavior()->CONNECTION_REQUIRED_TO_CHECK_DB_EXISTENCE && !checkConnected())
634         return false;
635     clearResult();
636 
637     if (d->driver->metaData()->isFileBased()) {
638         //for file-based db: file must exists and be accessible
639         QFileInfo file(d->connData.databaseName());
640         if (!file.exists() || (!file.isFile() && !file.isSymLink())) {
641             if (!ignoreErrors)
642                 m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
643                                      tr("The database file \"%1\" does not exist.")
644                                         .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName())));
645             return false;
646         }
647         if (!file.isReadable()) {
648             if (!ignoreErrors)
649                 m_result = KDbResult(ERR_ACCESS_RIGHTS,
650                                      tr("Database file \"%1\" is not readable.")
651                                         .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName())));
652             return false;
653         }
654         if (!d->options.isReadOnly() && !file.isWritable()) {
655             if (!ignoreErrors)
656                 m_result = KDbResult(ERR_ACCESS_RIGHTS,
657                                      tr("Database file \"%1\" is not writable.")
658                                         .arg(QDir::fromNativeSeparators(QFileInfo(d->connData.databaseName()).fileName())));
659             return false;
660         }
661         return true;
662     }
663 
664     QString tmpdbName;
665     //some engines need to have opened any database before executing "create database"
666     const bool orig_skipDatabaseExistsCheckInUseDatabase = d->skipDatabaseExistsCheckInUseDatabase;
667     d->skipDatabaseExistsCheckInUseDatabase = true;
668     bool ret = useTemporaryDatabaseIfNeeded(&tmpdbName);
669     d->skipDatabaseExistsCheckInUseDatabase = orig_skipDatabaseExistsCheckInUseDatabase;
670     if (!ret)
671         return false;
672 
673     ret = drv_databaseExists(dbName, ignoreErrors);
674 
675     if (!tmpdbName.isEmpty()) {
676         //whatever result is - now we have to close temporary opened database:
677         if (!closeDatabase())
678             return false;
679     }
680 
681     return ret;
682 }
683 
684 #define createDatabase_CLOSE \
685     { if (!closeDatabase()) { \
686             m_result = KDbResult(KDbConnection::tr("Database \"%1\" has been created but " \
687                                  "could not be closed after creation.").arg(dbName)); \
688             return false; \
689         } }
690 
691 #define createDatabase_ERROR \
692     { createDatabase_CLOSE; return false; }
693 
694 
createDatabase(const QString & dbName)695 bool KDbConnection::createDatabase(const QString &dbName)
696 {
697     if (d->driver->behavior()->CONNECTION_REQUIRED_TO_CREATE_DB && !checkConnected())
698         return false;
699 
700     if (databaseExists(dbName)) {
701         m_result = KDbResult(ERR_OBJECT_EXISTS,
702                              tr("Database \"%1\" already exists.").arg(dbName));
703         return false;
704     }
705     if (d->driver->isSystemDatabaseName(dbName)) {
706         m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED,
707                              tr("Could not create database \"%1\". This name is reserved for system database.").arg(dbName));
708         return false;
709     }
710     if (d->driver->metaData()->isFileBased()) {
711         //update connection data if filename differs
712         if (QFileInfo(dbName).isAbsolute()) {
713             d->connData.setDatabaseName(dbName);
714         }
715         else {
716             d->connData.setDatabaseName(
717                 QFileInfo(d->connData.databaseName()).absolutePath()
718                 + QDir::separator() +  QFileInfo(dbName).fileName());
719         }
720     }
721 
722     QString tmpdbName;
723     //some engines need to have opened any database before executing "create database"
724     if (!useTemporaryDatabaseIfNeeded(&tmpdbName))
725         return false;
726 
727     //low-level create
728     if (!drv_createDatabase(dbName)) {
729         m_result.prependMessage(tr("Error creating database \"%1\" on the server.").arg(dbName));
730         (void)closeDatabase();//sanity
731         return false;
732     }
733 
734     if (!tmpdbName.isEmpty()) {
735         //whatever result is - now we have to close temporary opened database:
736         if (!closeDatabase())
737             return false;
738     }
739 
740     if (!tmpdbName.isEmpty() || !d->driver->behavior()->IS_DB_OPEN_AFTER_CREATE) {
741         //db need to be opened
742         if (!useDatabase(dbName, false/*not yet kexi compatible!*/)) {
743             m_result = KDbResult(tr("Database \"%1\" has been created but could not be opened.").arg(dbName));
744             return false;
745         }
746     } else {
747         //just for the rule
748         d->usedDatabase = dbName;
749         d->isConnected = true;
750     }
751 
752     KDbTransaction trans;
753     if (d->driver->transactionsSupported()) {
754         trans = beginTransaction();
755         if (!trans.isActive())
756             return false;
757     }
758 
759     //-create system tables schema objects
760     d->setupKDbSystemSchema();
761 
762     //-physically create internal KDb tables
763     foreach(KDbInternalTableSchema* t, d->internalKDbTables()) {
764         if (!drv_createTable(*t))
765             createDatabase_ERROR;
766     }
767 
768     //-insert KDb version info:
769     // (for compatibility with Kexi expect the legacy kexidb_major_ver/kexidb_minor_ver values)
770     KDbTableSchema *table = d->table(QLatin1String("kexi__db"));
771     if (!table)
772         createDatabase_ERROR;
773     if (!insertRecord(table, QLatin1String("kexidb_major_ver"), KDb::version().major())
774             || !insertRecord(table, QLatin1String("kexidb_minor_ver"), KDb::version().minor()))
775         createDatabase_ERROR;
776 
777     if (trans.isActive() && !commitTransaction(trans))
778         createDatabase_ERROR;
779 
780     createDatabase_CLOSE;
781     return true;
782 }
783 
784 #undef createDatabase_CLOSE
785 #undef createDatabase_ERROR
786 
useDatabase(const QString & dbName,bool kexiCompatible,bool * cancelled,KDbMessageHandler * msgHandler)787 bool KDbConnection::useDatabase(const QString &dbName, bool kexiCompatible, bool *cancelled, KDbMessageHandler* msgHandler)
788 {
789     if (cancelled)
790         *cancelled = false;
791     //kdbDebug() << dbName << kexiCompatible;
792     if (!checkConnected())
793         return false;
794 
795     QString my_dbName;
796     if (dbName.isEmpty())
797         my_dbName = d->connData.databaseName();
798     else
799         my_dbName = dbName;
800     if (my_dbName.isEmpty())
801         return false;
802 
803     if (d->usedDatabase == my_dbName)
804         return true; //already used
805 
806     if (!d->skipDatabaseExistsCheckInUseDatabase) {
807         if (!databaseExists(my_dbName, false /*don't ignore errors*/))
808             return false; //database must exist
809     }
810 
811     if (!d->usedDatabase.isEmpty() && !closeDatabase()) //close db if already used
812         return false;
813 
814     d->usedDatabase.clear();
815 
816     if (!drv_useDatabase(my_dbName, cancelled, msgHandler)) {
817         if (cancelled && *cancelled)
818             return false;
819         QString msg(tr("Opening database \"%1\" failed.").arg(my_dbName));
820         m_result.prependMessage(msg);
821         return false;
822     }
823     if (d->serverVersion.isNull() && d->driver->behavior()->USING_DATABASE_REQUIRED_TO_CONNECT) {
824         // get version just now, it was not possible earlier
825         if (!drv_getServerVersion(&d->serverVersion))
826             return false;
827     }
828 
829     //-create system tables schema objects
830     d->setupKDbSystemSchema();
831 
832     if (kexiCompatible && my_dbName.compare(anyAvailableDatabaseName(), Qt::CaseInsensitive) != 0) {
833         //-get global database information
834         bool ok;
835         const int major = d->dbProperties.value(QLatin1String("kexidb_major_ver")).toInt(&ok);
836         if (!ok) {
837             m_result = d->dbProperties.result();
838             return false;
839         }
840         const int minor = d->dbProperties.value(QLatin1String("kexidb_minor_ver")).toInt(&ok);
841         if (!ok) {
842             m_result = d->dbProperties.result();
843             return false;
844         }
845         d->databaseVersion.setMajor(major);
846         d->databaseVersion.setMinor(minor);
847     }
848     d->usedDatabase = my_dbName;
849     return true;
850 }
851 
closeDatabase()852 bool KDbConnection::closeDatabase()
853 {
854     if (d->usedDatabase.isEmpty())
855         return true; //no db used
856     if (!checkConnected())
857         return true;
858 
859     bool ret = true;
860 
861     /*! @todo (js) add CLEVER algorithm here for nested transactions */
862     if (d->driver->transactionsSupported()) {
863         //rollback all transactions
864         d->dontRemoveTransactions = true; //lock!
865         foreach(const KDbTransaction& tr, d->transactions) {
866             if (!rollbackTransaction(tr)) {//rollback as much as you can, don't stop on prev. errors
867                 ret = false;
868             } else {
869                 kdbDebug() << "transaction rolled back!";
870                 kdbDebug() << "trans.refcount=="
871                            << (tr.m_data ? QString::number(tr.m_data->refcount())
872                                          : QLatin1String("(null)"));
873             }
874         }
875         d->dontRemoveTransactions = false; //unlock!
876         d->transactions.clear(); //free trans. data
877     }
878 
879     //delete own cursors:
880     d->deleteAllCursors();
881     //delete own schemas
882     d->clearTables();
883     d->clearQueries();
884 
885     if (!drv_closeDatabase())
886         return false;
887 
888     d->usedDatabase.clear();
889     return ret;
890 }
891 
currentDatabase() const892 QString KDbConnection::currentDatabase() const
893 {
894     return d->usedDatabase;
895 }
896 
useTemporaryDatabaseIfNeeded(QString * name)897 bool KDbConnection::useTemporaryDatabaseIfNeeded(QString* name)
898 {
899     if (d->driver->behavior()->USE_TEMPORARY_DATABASE_FOR_CONNECTION_IF_NEEDED && !isDatabaseUsed()) {
900         //we have no db used, but it is required by engine to have used any!
901         *name = anyAvailableDatabaseName();
902         if (name->isEmpty()) {
903             m_result = KDbResult(ERR_NO_DB_USED,
904                                  tr("Could not find any database for temporary connection."));
905             return false;
906         }
907         const bool orig_skipDatabaseExistsCheckInUseDatabase = d->skipDatabaseExistsCheckInUseDatabase;
908         d->skipDatabaseExistsCheckInUseDatabase = true;
909         bool ret = useDatabase(*name, false);
910         d->skipDatabaseExistsCheckInUseDatabase = orig_skipDatabaseExistsCheckInUseDatabase;
911         if (!ret) {
912             m_result = KDbResult(m_result.code(),
913                                  tr("Error during starting temporary connection using \"%1\" database name.").arg(*name));
914             return false;
915         }
916     }
917     return true;
918 }
919 
dropDatabase(const QString & dbName)920 bool KDbConnection::dropDatabase(const QString &dbName)
921 {
922     if (d->driver->behavior()->CONNECTION_REQUIRED_TO_DROP_DB && !checkConnected())
923         return false;
924 
925     QString dbToDrop;
926     if (dbName.isEmpty() && d->usedDatabase.isEmpty()) {
927         if (!d->driver->metaData()->isFileBased()
928                 || (d->driver->metaData()->isFileBased() && d->connData.databaseName().isEmpty()))
929         {
930             m_result = KDbResult(ERR_NO_NAME_SPECIFIED,
931                                  tr("Could not delete database. Name is not specified."));
932             return false;
933         }
934         //this is a file driver so reuse previously passed filename
935         dbToDrop = d->connData.databaseName();
936     } else {
937         if (dbName.isEmpty()) {
938             dbToDrop = d->usedDatabase;
939         } else {
940             if (d->driver->metaData()->isFileBased()) //lets get full path
941                 dbToDrop = QFileInfo(dbName).absoluteFilePath();
942             else
943                 dbToDrop = dbName;
944         }
945     }
946 
947     if (dbToDrop.isEmpty()) {
948         m_result = KDbResult(ERR_NO_NAME_SPECIFIED,
949                              tr("Could not delete database. Name is not specified."));
950         return false;
951     }
952 
953     if (d->driver->isSystemDatabaseName(dbToDrop)) {
954         m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED,
955                              tr("Could not delete system database \"%1\".").arg(dbToDrop));
956         return false;
957     }
958 
959     if (isDatabaseUsed() && d->usedDatabase == dbToDrop) {
960         //we need to close database because cannot drop used this database
961         if (!closeDatabase())
962             return false;
963     }
964 
965     QString tmpdbName;
966     //some engines need to have opened any database before executing "drop database"
967     if (!useTemporaryDatabaseIfNeeded(&tmpdbName))
968         return false;
969 
970     //ok, now we have access to dropping
971     bool ret = drv_dropDatabase(dbToDrop);
972 
973     if (!tmpdbName.isEmpty()) {
974         //whatever result is - now we have to close temporary opened database:
975         if (!closeDatabase())
976             return false;
977     }
978     return ret;
979 }
980 
objectNames(int objectType,bool * ok)981 QStringList KDbConnection::objectNames(int objectType, bool* ok)
982 {
983     if (!checkIsDatabaseUsed()) {
984         if (ok) {
985             *ok = false;
986         }
987         return QStringList();
988     }
989     KDbEscapedString sql;
990     if (objectType == KDb::AnyObjectType) {
991         sql = "SELECT o_name FROM kexi__objects ORDER BY o_id";
992     } else {
993         sql = KDbEscapedString("SELECT o_name FROM kexi__objects WHERE o_type=%1"
994                                " ORDER BY o_id").arg(d->driver->valueToSql(KDbField::Integer, objectType));
995     }
996     QStringList list;
997     const bool success = queryStringListInternal(&sql, &list, nullptr, nullptr, 0, KDb::isIdentifier);
998     if (ok) {
999         *ok = success;
1000     }
1001     if (!success) {
1002         m_result.prependMessage(tr("Could not retrieve list of object names."));
1003     }
1004     return list;
1005 }
1006 
tableNames(bool alsoSystemTables,bool * ok)1007 QStringList KDbConnection::tableNames(bool alsoSystemTables, bool* ok)
1008 {
1009     QStringList result;
1010     bool success;
1011     if (!ok) {
1012         ok = &success;
1013     }
1014     QStringList list = objectNames(KDb::TableObjectType, ok);
1015     if (!*ok) {
1016         m_result.prependMessage(tr("Could not retrieve list of table names."));
1017         return QStringList();
1018     }
1019     if (alsoSystemTables) {
1020         list += kdbSystemTableNames();
1021     }
1022     const QStringList physicalTableNames = drv_getTableNames(ok);
1023     if (!*ok) {
1024         m_result.prependMessage(tr("Could not retrieve list of physical table names."));
1025         return QStringList();
1026     }
1027     QSet<QString> physicalTableNamesSet;
1028     for (const QString &name : physicalTableNames) {
1029         physicalTableNamesSet.insert(name.toLower());
1030     }
1031     for (const QString &name : list) {
1032         if (physicalTableNamesSet.contains(name.toLower())) {
1033             result += name;
1034         }
1035     }
1036     return result;
1037 }
1038 
containsTable(const QString & tableName)1039 tristate KDbConnection::containsTable(const QString &tableName)
1040 {
1041     return drv_containsTable(tableName);
1042 }
1043 
kdbSystemTableNames()1044 QStringList KDbConnection::kdbSystemTableNames()
1045 {
1046     return *g_kdbSystemTableNames;
1047 }
1048 
serverVersion() const1049 KDbServerVersionInfo KDbConnection::serverVersion() const
1050 {
1051     return isConnected() ? d->serverVersion : KDbServerVersionInfo();
1052 }
1053 
databaseVersion() const1054 KDbVersionInfo KDbConnection::databaseVersion() const
1055 {
1056     return isDatabaseUsed() ? d->databaseVersion : KDbVersionInfo();
1057 }
1058 
databaseProperties() const1059 KDbProperties KDbConnection::databaseProperties() const
1060 {
1061     return d->dbProperties;
1062 }
1063 
tableIds(bool * ok)1064 QList<int> KDbConnection::tableIds(bool* ok)
1065 {
1066     return objectIds(KDb::TableObjectType, ok);
1067 }
1068 
queryIds(bool * ok)1069 QList<int> KDbConnection::queryIds(bool* ok)
1070 {
1071     return objectIds(KDb::QueryObjectType, ok);
1072 }
1073 
objectIds(int objectType,bool * ok)1074 QList<int> KDbConnection::objectIds(int objectType, bool* ok)
1075 {
1076     if (!checkIsDatabaseUsed())
1077         return QList<int>();
1078 
1079     KDbEscapedString sql;
1080     if (objectType == KDb::AnyObjectType)
1081         sql = "SELECT o_id, o_name FROM kexi__objects ORDER BY o_id";
1082     else
1083         sql = "SELECT o_id, o_name FROM kexi__objects WHERE o_type=" + QByteArray::number(objectType)
1084               + " ORDER BY o_id";
1085 
1086     KDbCursor *c = executeQuery(sql);
1087     if (!c) {
1088         if (ok) {
1089             *ok = false;
1090         }
1091         m_result.prependMessage(tr("Could not retrieve list of object identifiers."));
1092         return QList<int>();
1093     }
1094     QList<int> list;
1095     for (c->moveFirst(); !c->eof(); c->moveNext()) {
1096         QString tname = c->value(1).toString(); //kexi__objects.o_name
1097         if (KDb::isIdentifier(tname)) {
1098             list.append(c->value(0).toInt()); //kexi__objects.o_id
1099         }
1100     }
1101     deleteCursor(c);
1102     if (ok) {
1103         *ok = true;
1104     }
1105     return list;
1106 }
1107 
1108 //yeah, it is very efficient:
1109 #define C_A(a) , const QVariant& c ## a
1110 
1111 #define V_A0 d->driver->valueToSql( tableSchema->field(0), c0 )
1112 #define V_A(a) + ',' + d->driver->valueToSql( \
1113         tableSchema->field(a) ? tableSchema->field(a)->type() : KDbField::Text, c ## a )
1114 
1115 //  kdbDebug() << "******** " << QString("INSERT INTO ") +
1116 //   escapeIdentifier(tableSchema->name()) +
1117 //   " VALUES (" + vals + ")";
1118 
insertRecordInternal(const QString & tableSchemaName,KDbFieldList * fields,const KDbEscapedString & sql)1119 QSharedPointer<KDbSqlResult> KDbConnection::insertRecordInternal(const QString &tableSchemaName,
1120                                                                  KDbFieldList *fields,
1121                                                                  const KDbEscapedString &sql)
1122 {
1123     QSharedPointer<KDbSqlResult> res;
1124     if (!drv_beforeInsert(tableSchemaName,fields )) {
1125         return res;
1126     }
1127     res = prepareSql(sql);
1128     if (!res || res->lastResult().isError()) {
1129         res.clear();
1130         return res;
1131     }
1132     if (!drv_afterInsert(tableSchemaName, fields)) {
1133         res.clear();
1134         return res;
1135     }
1136     {
1137         // Fetching is needed to perform real execution at least for some backends.
1138         // Also we are not expecting record but let's delete if there's any.
1139         QSharedPointer<KDbSqlRecord> record = res->fetchRecord();
1140         Q_UNUSED(record)
1141     }
1142     if (res->lastResult().isError()) {
1143         res.clear();
1144     }
1145     return res;
1146 }
1147 
1148 #define C_INS_REC(args, vals) \
1149     QSharedPointer<KDbSqlResult> KDbConnection::insertRecord(KDbTableSchema* tableSchema args) { \
1150         return insertRecordInternal(tableSchema->name(), tableSchema, \
1151                    KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableSchema->name()) \
1152                        + " (" \
1153                        + tableSchema->sqlFieldsList(this) \
1154                        + ") VALUES (" + vals + ')'); \
1155     }
1156 
1157 #define C_INS_REC_ALL \
1158     C_INS_REC( C_A(0), V_A0 ) \
1159     C_INS_REC( C_A(0) C_A(1), V_A0 V_A(1) ) \
1160     C_INS_REC( C_A(0) C_A(1) C_A(2), V_A0 V_A(1) V_A(2) ) \
1161     C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3), V_A0 V_A(1) V_A(2) V_A(3) ) \
1162     C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) ) \
1163     C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) ) \
1164     C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) ) \
1165     C_INS_REC( C_A(0) C_A(1) C_A(2) C_A(3) C_A(4) C_A(5) C_A(6) C_A(7), V_A0 V_A(1) V_A(2) V_A(3) V_A(4) V_A(5) V_A(6) V_A(7) )
1166 
1167 C_INS_REC_ALL
1168 
1169 #undef V_A0
1170 #undef V_A
1171 #undef C_INS_REC
1172 
1173 #define V_A0 value += d->driver->valueToSql( it.next(), c0 );
1174 #define V_A( a ) value += (',' + d->driver->valueToSql( it.next(), c ## a ));
1175 
1176 #define C_INS_REC(args, vals) \
1177     QSharedPointer<KDbSqlResult> KDbConnection::insertRecord(KDbFieldList* fields args) \
1178     { \
1179         KDbEscapedString value; \
1180         const KDbField::List *flist = fields->fields(); \
1181         QListIterator<KDbField*> it(*flist); \
1182         vals \
1183         it.toFront(); \
1184         QString tableName((it.hasNext() && it.peekNext()->table()) ? it.next()->table()->name() : QLatin1String("??")); \
1185         return insertRecordInternal(tableName, fields, \
1186                    KDbEscapedString(QLatin1String("INSERT INTO ") + escapeIdentifier(tableName)) \
1187                    + " (" + fields->sqlFieldsList(this) \
1188                    + ") VALUES (" + value + ')'); \
1189     }
1190 
1191 C_INS_REC_ALL
1192 
1193 #undef C_A
1194 #undef V_A
1195 #undef V_ALAST
1196 #undef C_INS_REC
1197 #undef C_INS_REC_ALL
1198 
insertRecord(KDbTableSchema * tableSchema,const QList<QVariant> & values)1199 QSharedPointer<KDbSqlResult> KDbConnection::insertRecord(KDbTableSchema *tableSchema,
1200                                                          const QList<QVariant> &values)
1201 {
1202 // Each SQL identifier needs to be escaped in the generated query.
1203     QSharedPointer<KDbSqlResult> res;
1204     const KDbField::List *flist = tableSchema->fields();
1205     if (flist->isEmpty()) {
1206         return res;
1207     }
1208     KDbField::ListIterator fieldsIt(flist->constBegin());
1209     QList<QVariant>::ConstIterator it = values.constBegin();
1210     KDbEscapedString sql;
1211     sql.reserve(4096);
1212     while (fieldsIt != flist->constEnd() && (it != values.end())) {
1213         KDbField *f = *fieldsIt;
1214         if (sql.isEmpty()) {
1215             sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableSchema->name())
1216                   + " VALUES (";
1217         }
1218         else {
1219             sql += ',';
1220         }
1221         sql += d->driver->valueToSql(f, *it);
1222 //  kdbDebug() << "val" << i++ << ": " << d->driver->valueToSql( f, *it );
1223         ++it;
1224         ++fieldsIt;
1225     }
1226     sql += ')';
1227     m_result.setSql(sql);
1228     res = insertRecordInternal(tableSchema->name(), tableSchema, sql);
1229     return res;
1230 }
1231 
insertRecord(KDbFieldList * fields,const QList<QVariant> & values)1232 QSharedPointer<KDbSqlResult> KDbConnection::insertRecord(KDbFieldList *fields,
1233                                                          const QList<QVariant> &values)
1234 {
1235 // Each SQL identifier needs to be escaped in the generated query.
1236     QSharedPointer<KDbSqlResult> res;
1237     const KDbField::List *flist = fields->fields();
1238     if (flist->isEmpty()) {
1239         return res;
1240     }
1241     KDbField::ListIterator fieldsIt(flist->constBegin());
1242     KDbEscapedString sql;
1243     sql.reserve(4096);
1244     QList<QVariant>::ConstIterator it = values.constBegin();
1245     const QString tableName(flist->first()->table()->name());
1246     while (fieldsIt != flist->constEnd() && it != values.constEnd()) {
1247         KDbField *f = *fieldsIt;
1248         if (sql.isEmpty()) {
1249             sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(tableName) + '(' +
1250                   fields->sqlFieldsList(this) + ") VALUES (";
1251         }
1252         else {
1253             sql += ',';
1254         }
1255         sql += d->driver->valueToSql(f, *it);
1256 //  kdbDebug() << "val" << i++ << ": " << d->driver->valueToSql( f, *it );
1257         ++it;
1258         ++fieldsIt;
1259         if (fieldsIt == flist->constEnd())
1260             break;
1261     }
1262     sql += ')';
1263     m_result.setSql(sql);
1264     res = insertRecordInternal(tableName, fields, sql);
1265     return res;
1266 }
1267 
checkSql(const KDbEscapedString & sql,KDbResult * result)1268 inline static bool checkSql(const KDbEscapedString& sql, KDbResult* result)
1269 {
1270     Q_ASSERT(result);
1271     if (!sql.isValid()) {
1272         *result = KDbResult(ERR_SQL_EXECUTION_ERROR,
1273                             KDbConnection::tr("SQL statement for execution is invalid or empty."));
1274         result->setErrorSql(sql); //remember for error handling
1275         return false;
1276     }
1277     return true;
1278 }
1279 
prepareSql(const KDbEscapedString & sql)1280 QSharedPointer<KDbSqlResult> KDbConnection::prepareSql(const KDbEscapedString& sql)
1281 {
1282     m_result.setSql(sql);
1283     return QSharedPointer<KDbSqlResult>(drv_prepareSql(sql));
1284 }
1285 
executeSql(const KDbEscapedString & sql)1286 bool KDbConnection::executeSql(const KDbEscapedString& sql)
1287 {
1288     m_result.setSql(sql);
1289     if (!checkSql(sql, &m_result)) {
1290         return false;
1291     }
1292     if (!drv_executeSql(sql)) {
1293         m_result.setMessage(QString()); //clear as this could be most probably just "Unknown error" string.
1294         m_result.setErrorSql(sql);
1295         m_result.prependMessage(ERR_SQL_EXECUTION_ERROR,
1296                                 tr("Error while executing SQL statement."));
1297         kdbWarning() << m_result;
1298         return false;
1299     }
1300     return true;
1301 }
1302 
findSystemFieldName(const KDbFieldList & fieldlist)1303 KDbField* KDbConnection::findSystemFieldName(const KDbFieldList& fieldlist)
1304 {
1305     for (KDbField::ListIterator it(fieldlist.fieldsIterator()); it != fieldlist.fieldsIteratorConstEnd(); ++it) {
1306         if (d->driver->isSystemFieldName((*it)->name()))
1307             return *it;
1308     }
1309     return nullptr;
1310 }
1311 
1312 //! Creates a KDbField list for kexi__fields, for sanity. Used by createTable()
createFieldListForKexi__Fields(KDbTableSchema * kexi__fieldsSchema)1313 static KDbFieldList* createFieldListForKexi__Fields(KDbTableSchema *kexi__fieldsSchema)
1314 {
1315     if (!kexi__fieldsSchema)
1316         return nullptr;
1317     return kexi__fieldsSchema->subList(
1318                QList<QByteArray>()
1319                << "t_id"
1320                << "f_type"
1321                << "f_name"
1322                << "f_length"
1323                << "f_precision"
1324                << "f_constraints"
1325                << "f_options"
1326                << "f_default"
1327                << "f_order"
1328                << "f_caption"
1329                << "f_help"
1330            );
1331 }
1332 
buildLengthValue(const KDbField & f)1333 static QVariant buildLengthValue(const KDbField &f)
1334 {
1335     if (f.isFPNumericType()) {
1336         return f.scale();
1337     }
1338     return f.maxLength();
1339 }
1340 
1341 //! builds a list of values for field's @a f properties. Used by createTable().
buildValuesForKexi__Fields(QList<QVariant> & vals,KDbField * f)1342 static void buildValuesForKexi__Fields(QList<QVariant>& vals, KDbField* f)
1343 {
1344     const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive
1345     vals.clear();
1346     vals
1347     << QVariant(f->table()->id())
1348     << QVariant(type)
1349     << QVariant(f->name())
1350     << buildLengthValue(*f)
1351     << QVariant(KDbField::isFPNumericType(type) ? f->precision() : 0)
1352     << QVariant(f->constraints())
1353     << QVariant(f->options())
1354     // KDb::variantToString() is needed here because the value can be of any QVariant type,
1355     // depending on f->type()
1356     << (f->defaultValue().isNull()
1357         ? QVariant() : QVariant(KDb::variantToString(f->defaultValue())))
1358     << QVariant(f->order())
1359     << QVariant(f->caption())
1360     << QVariant(f->description());
1361 }
1362 
storeMainFieldSchema(KDbField * field)1363 bool KDbConnection::storeMainFieldSchema(KDbField *field)
1364 {
1365     if (!field || !field->table())
1366         return false;
1367     KDbFieldList *fl = createFieldListForKexi__Fields(d->table(QLatin1String("kexi__fields")));
1368     if (!fl)
1369         return false;
1370 
1371     QList<QVariant> vals;
1372     buildValuesForKexi__Fields(vals, field);
1373     QList<QVariant>::ConstIterator valsIt = vals.constBegin();
1374     bool first = true;
1375     KDbEscapedString sql("UPDATE kexi__fields SET ");
1376     foreach(KDbField *f, *fl->fields()) {
1377         sql.append((first ? QString() : QLatin1String(", ")) +
1378                    f->name() + QLatin1Char('=') + d->driver->valueToSql(f, *valsIt));
1379         if (first)
1380             first = false;
1381         ++valsIt;
1382     }
1383     delete fl;
1384 
1385     sql.append(KDbEscapedString(" WHERE t_id=%1 AND f_name=%2")
1386                 .arg(d->driver->valueToSql(KDbField::Integer, field->table()->id()))
1387                 .arg(escapeString(field->name())));
1388     return executeSql(sql);
1389 }
1390 
1391 #define createTable_ERR \
1392     { kdbDebug() << "ERROR!"; \
1393         m_result.prependMessage(KDbConnection::tr("Creating table failed.")); \
1394         rollbackAutoCommitTransaction(tg.transaction()); \
1395         return false; }
1396 
createTable(KDbTableSchema * tableSchema,CreateTableOptions options)1397 bool KDbConnection::createTable(KDbTableSchema* tableSchema, CreateTableOptions options)
1398 {
1399     if (!tableSchema || !checkIsDatabaseUsed())
1400         return false;
1401 
1402     //check if there are any fields
1403     if (tableSchema->fieldCount() < 1) {
1404         clearResult();
1405         m_result = KDbResult(ERR_CANNOT_CREATE_EMPTY_OBJECT,
1406                              tr("Could not create table without fields."));
1407         return false;
1408     }
1409     KDbInternalTableSchema* internalTable = dynamic_cast<KDbInternalTableSchema*>(tableSchema);
1410     const QString tableName(tableSchema->name());
1411 
1412     if (!internalTable) {
1413         if (d->driver->isSystemObjectName(tableName)) {
1414             clearResult();
1415             m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED,
1416                                  tr("System name \"%1\" cannot be used as table name.")
1417                                     .arg(tableSchema->name()));
1418             return false;
1419         }
1420 
1421         KDbField *sys_field = findSystemFieldName(*tableSchema);
1422         if (sys_field) {
1423             clearResult();
1424             m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED,
1425                                  tr("System name \"%1\" cannot be used as one of fields in \"%2\" table.")
1426                                     .arg(sys_field->name(), tableName));
1427             return false;
1428         }
1429     }
1430 
1431     bool previousSchemaStillKept = false;
1432 
1433     KDbTableSchema *existingTable = nullptr;
1434     if (options & CreateTableOption::DropDestination) {
1435         //get previous table (do not retrieve, though)
1436         existingTable = this->tableSchema(tableName);
1437         if (existingTable) {
1438             if (existingTable == tableSchema) {
1439                 clearResult();
1440                 m_result = KDbResult(ERR_OBJECT_EXISTS,
1441                                      tr("Could not create the same table \"%1\" twice.").arg(tableSchema->name()));
1442                 return false;
1443             }
1444 //! @todo (js) update any structure (e.g. queries) that depend on this table!
1445             if (existingTable->id() > 0)
1446                 tableSchema->setId(existingTable->id()); //copy id from existing table
1447             previousSchemaStillKept = true;
1448             if (!dropTableInternal(existingTable, false /*alsoRemoveSchema*/))
1449                 return false;
1450         }
1451     } else {
1452         if (!internalTable && this->tableSchema(tableSchema->name())) {
1453             clearResult();
1454             m_result = KDbResult(ERR_OBJECT_EXISTS,
1455                                  tr("Table \"%1\" already exists.").arg(tableSchema->name()));
1456             return false;
1457         }
1458     }
1459     KDbTransactionGuard tg;
1460     if (!beginAutoCommitTransaction(&tg))
1461         return false;
1462 
1463     if (internalTable) {
1464         if (!drv_containsTable(internalTable->name())) { // internal table may exist
1465             if (!drv_createTable(*tableSchema)) {
1466                 createTable_ERR;
1467             }
1468         }
1469     } else {
1470         if (!drv_createTable(*tableSchema)) {
1471             createTable_ERR;
1472         }
1473     }
1474 
1475     //add the object data to kexi__* tables
1476     if (!internalTable) {
1477         //update kexi__objects
1478         if (!storeNewObjectData(tableSchema))
1479             createTable_ERR;
1480 
1481         KDbTableSchema *ts = d->table(QLatin1String("kexi__fields"));
1482         if (!ts)
1483             return false;
1484         //for sanity: remove field info (if any) for this table id
1485         if (!KDb::deleteRecords(this, *ts, QLatin1String("t_id"), tableSchema->id()))
1486             return false;
1487 
1488         KDbFieldList *fl = createFieldListForKexi__Fields(ts);
1489         if (!fl)
1490             return false;
1491 
1492         foreach(KDbField *f, *tableSchema->fields()) {
1493             QList<QVariant> vals;
1494             buildValuesForKexi__Fields(vals, f);
1495             if (!insertRecord(fl, vals))
1496                 createTable_ERR;
1497         }
1498         delete fl;
1499 
1500         if (!storeExtendedTableSchemaData(tableSchema))
1501             createTable_ERR;
1502     }
1503     bool res = commitAutoCommitTransaction(tg.transaction());
1504     if (res) {
1505         if (!internalTable) {
1506             if (previousSchemaStillKept) {
1507                 //remove previous table schema
1508                 d->removeTable(tableSchema->id());
1509             }
1510         }
1511         //store locally
1512         d->insertTable(tableSchema);
1513         //ok, this table is not created by the connection
1514         tableSchema->setConnection(this);
1515     }
1516     return res;
1517 }
1518 
copyTable(const KDbTableSchema & tableSchema,const KDbObject & newData)1519 KDbTableSchema *KDbConnection::copyTable(const KDbTableSchema &tableSchema, const KDbObject &newData)
1520 {
1521     clearResult();
1522     if (this->tableSchema(tableSchema.name()) != &tableSchema) {
1523         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1524                              tr("Table \"%1\" does not exist.").arg(tableSchema.name()));
1525         return nullptr;
1526     }
1527     KDbTableSchema *copiedTable = new KDbTableSchema(tableSchema, false /* !copyId*/);
1528     // copy name, caption, description
1529     copiedTable->setName(newData.name());
1530     copiedTable->setCaption(newData.caption());
1531     copiedTable->setDescription(newData.description());
1532     // copy the structure and data
1533     if (!createTable(copiedTable,
1534         CreateTableOptions(CreateTableOption::Default) & ~CreateTableOptions(CreateTableOption::DropDestination)))
1535     {
1536         delete copiedTable;
1537         return nullptr;
1538     }
1539     if (!drv_copyTableData(tableSchema, *copiedTable)) {
1540         dropTable(copiedTable);
1541         delete copiedTable;
1542         return nullptr;
1543     }
1544     return copiedTable;
1545 }
1546 
copyTable(const QString & tableName,const KDbObject & newData)1547 KDbTableSchema *KDbConnection::copyTable(const QString &tableName, const KDbObject &newData)
1548 {
1549     clearResult();
1550     KDbTableSchema* ts = tableSchema(tableName);
1551     if (!ts) {
1552         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1553                              tr("Table \"%1\" does not exist.").arg(tableName));
1554         return nullptr;
1555     }
1556     return copyTable(*ts, newData);
1557 }
1558 
drv_copyTableData(const KDbTableSchema & tableSchema,const KDbTableSchema & destinationTableSchema)1559 bool KDbConnection::drv_copyTableData(const KDbTableSchema &tableSchema,
1560                                    const KDbTableSchema &destinationTableSchema)
1561 {
1562     KDbEscapedString sql = KDbEscapedString("INSERT INTO %1 SELECT * FROM %2")
1563                 .arg(escapeIdentifier(destinationTableSchema.name()))
1564                 .arg(escapeIdentifier(tableSchema.name()));
1565     return executeSql(sql);
1566 }
1567 
removeObject(int objId)1568 bool KDbConnection::removeObject(int objId)
1569 {
1570     clearResult();
1571     //remove table schema from kexi__* tables
1572     KDbTableSchema *kexi__objects = d->table(QLatin1String("kexi__objects"));
1573     KDbTableSchema *kexi__objectdata = d->table(QLatin1String("kexi__objectdata"));
1574     if (!kexi__objects || !kexi__objectdata
1575         || !KDb::deleteRecords(this, *kexi__objects, QLatin1String("o_id"), objId) //schema entry
1576         || !KDb::deleteRecords(this, *kexi__objectdata, QLatin1String("o_id"), objId)) //data blocks
1577     {
1578         m_result = KDbResult(ERR_DELETE_SERVER_ERROR,
1579                              tr("Could not delete object's data."));
1580         return false;
1581     }
1582     return true;
1583 }
1584 
drv_dropTable(const QString & tableName)1585 bool KDbConnection::drv_dropTable(const QString& tableName)
1586 {
1587     return executeSql(KDbEscapedString("DROP TABLE %1").arg(escapeIdentifier(tableName)));
1588 }
1589 
dropTable(KDbTableSchema * tableSchema)1590 tristate KDbConnection::dropTable(KDbTableSchema* tableSchema)
1591 {
1592     return dropTableInternal(tableSchema, true);
1593 }
1594 
dropTableInternal(KDbTableSchema * tableSchema,bool alsoRemoveSchema)1595 tristate KDbConnection::dropTableInternal(KDbTableSchema* tableSchema, bool alsoRemoveSchema)
1596 {
1597     // Each SQL identifier needs to be escaped in the generated query.
1598     clearResult();
1599     if (!tableSchema)
1600         return false;
1601 
1602     //be sure that we handle the correct KDbTableSchema object:
1603     if (tableSchema->id() < 0
1604             || this->tableSchema(tableSchema->name()) != tableSchema
1605             || this->tableSchema(tableSchema->id()) != tableSchema) {
1606         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1607                              tr("Could not delete table \"%1\". %2")
1608                                .arg(tr("Unexpected name or identifier."),
1609                                     tableSchema->name()));
1610         return false;
1611     }
1612 
1613     tristate res = KDbTableSchemaChangeListener::closeListeners(this, tableSchema);
1614     if (true != res)
1615         return res;
1616 
1617     //sanity checks:
1618     if (d->driver->isSystemObjectName(tableSchema->name())) {
1619         m_result = KDbResult(ERR_SYSTEM_NAME_RESERVED,
1620                              tr("Could not delete table \"%1\". %2")
1621                                 .arg(tableSchema->name(),
1622                                      d->strItIsASystemObject()));
1623         return false;
1624     }
1625 
1626     KDbTransactionGuard tg;
1627     if (!beginAutoCommitTransaction(&tg))
1628         return false;
1629 
1630     //for sanity we're checking if this table exists physically
1631     const tristate result = drv_containsTable(tableSchema->name());
1632     if (~result) {
1633         return cancelled;
1634     }
1635     if (result == true) {
1636         if (!drv_dropTable(tableSchema->name()))
1637             return false;
1638     }
1639 
1640     KDbTableSchema *ts = d->table(QLatin1String("kexi__fields"));
1641     if (!ts || !KDb::deleteRecords(this, *ts, QLatin1String("t_id"), tableSchema->id())) //field entries
1642         return false;
1643 
1644     //remove table schema from kexi__objects table
1645     if (!removeObject(tableSchema->id())) {
1646         return false;
1647     }
1648 
1649     if (alsoRemoveSchema) {
1650 //! @todo js: update any structure (e.g. queries) that depend on this table!
1651         tristate res = removeDataBlock(tableSchema->id(), QLatin1String("extended_schema"));
1652         if (!res)
1653             return false;
1654         d->removeTable(tableSchema->id());
1655     }
1656     return commitAutoCommitTransaction(tg.transaction());
1657 }
1658 
dropTable(const QString & tableName)1659 tristate KDbConnection::dropTable(const QString& tableName)
1660 {
1661     clearResult();
1662     KDbTableSchema* ts = tableSchema(tableName);
1663     if (!ts) {
1664         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1665                              tr("Table \"%1\" does not exist.").arg(tableName));
1666         return false;
1667     }
1668     return dropTable(ts);
1669 }
1670 
alterTable(KDbTableSchema * tableSchema,KDbTableSchema * newTableSchema)1671 tristate KDbConnection::alterTable(KDbTableSchema* tableSchema, KDbTableSchema* newTableSchema)
1672 {
1673     clearResult();
1674     tristate res = KDbTableSchemaChangeListener::closeListeners(this, tableSchema);
1675     if (true != res)
1676         return res;
1677 
1678     if (tableSchema == newTableSchema) {
1679         m_result = KDbResult(ERR_OBJECT_THE_SAME,
1680                              tr("Could not alter table \"%1\" using the same table as destination.")
1681                                 .arg(tableSchema->name()));
1682         return false;
1683     }
1684 //! @todo (js) implement real altering
1685 //! @todo (js) update any structure (e.g. query) that depend on this table!
1686     bool ok = true;
1687     bool empty;
1688 #if 0 //! @todo uncomment:
1689     empty = isEmpty(tableSchema, ok) && ok;
1690 #else
1691     empty = true;
1692 #endif
1693     if (empty) {
1694         ok = createTable(newTableSchema, KDbConnection::CreateTableOption::Default
1695                              | KDbConnection::CreateTableOption::DropDestination);
1696     }
1697     return ok;
1698 }
1699 
alterTableName(KDbTableSchema * tableSchema,const QString & newName,AlterTableNameOptions options)1700 bool KDbConnection::alterTableName(KDbTableSchema* tableSchema, const QString& newName,
1701                                    AlterTableNameOptions options)
1702 {
1703     clearResult();
1704     if (tableSchema != this->tableSchema(tableSchema->id())) {
1705         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1706                              tr("Unknown table \"%1\".").arg(tableSchema->name()));
1707         return false;
1708     }
1709     if (newName.isEmpty() || !KDb::isIdentifier(newName)) {
1710         m_result = KDbResult(ERR_INVALID_IDENTIFIER,
1711                              tr("Invalid table name \"%1\".").arg(newName));
1712         return false;
1713     }
1714     const QString oldTableName = tableSchema->name();
1715     const QString newTableName = newName.trimmed();
1716     if (oldTableName.trimmed() == newTableName) {
1717         m_result = KDbResult(ERR_OBJECT_THE_SAME,
1718                              tr("Could not rename table \"%1\" using the same name.")
1719                                 .arg(newTableName));
1720         return false;
1721     }
1722 //! @todo alter table name for server DB backends!
1723 //! @todo what about objects (queries/forms) that use old name?
1724     KDbTableSchema *tableToReplace = this->tableSchema(newName);
1725     const bool destTableExists = tableToReplace != nullptr;
1726     const int origID = destTableExists ? tableToReplace->id() : -1; //will be reused in the new table
1727     if (!(options & AlterTableNameOption::DropDestination) && destTableExists) {
1728         m_result = KDbResult(ERR_OBJECT_EXISTS,
1729                              tr("Could not rename table \"%1\" to \"%2\". Table \"%3\" already exists.")
1730                                 .arg(tableSchema->name(), newName, newName));
1731         return false;
1732     }
1733 
1734 //helper:
1735 #define alterTableName_ERR \
1736     tableSchema->setName(oldTableName) //restore old name
1737 
1738     KDbTransactionGuard tg;
1739     if (!beginAutoCommitTransaction(&tg))
1740         return false;
1741 
1742     // drop the table replaced (with schema)
1743     if (destTableExists) {
1744         if (!dropTable(newName)) {
1745             return false;
1746         }
1747 
1748         // the new table owns the previous table's id:
1749         if (!executeSql(
1750                     KDbEscapedString("UPDATE kexi__objects SET o_id=%1 WHERE o_id=%2 AND o_type=%3")
1751                     .arg(d->driver->valueToSql(KDbField::Integer, origID))
1752                     .arg(d->driver->valueToSql(KDbField::Integer, tableSchema->id()))
1753                     .arg(d->driver->valueToSql(KDbField::Integer, int(KDb::TableObjectType)))))
1754         {
1755             return false;
1756         }
1757         if (!executeSql(KDbEscapedString("UPDATE kexi__fields SET t_id=%1 WHERE t_id=%2")
1758                         .arg(d->driver->valueToSql(KDbField::Integer, origID))
1759                         .arg(d->driver->valueToSql(KDbField::Integer, tableSchema->id()))))
1760         {
1761             return false;
1762         }
1763 
1764         //maintain table ID
1765         d->changeTableId(tableSchema, origID);
1766         tableSchema->setId(origID);
1767     }
1768 
1769     if (!drv_alterTableName(tableSchema, newTableName)) {
1770         alterTableName_ERR;
1771         return false;
1772     }
1773 
1774     // Update kexi__objects
1775     //! @todo
1776     if (!executeSql(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2")
1777                     .arg(escapeString(tableSchema->name()))
1778                     .arg(d->driver->valueToSql(KDbField::Integer, tableSchema->id()))))
1779     {
1780         alterTableName_ERR;
1781         return false;
1782     }
1783 //! @todo what about caption?
1784 
1785     //restore old name: it will be changed soon!
1786     tableSchema->setName(oldTableName);
1787 
1788     if (!commitAutoCommitTransaction(tg.transaction())) {
1789         alterTableName_ERR;
1790         return false;
1791     }
1792 
1793     //update tableSchema:
1794     d->renameTable(tableSchema, newTableName);
1795     return true;
1796 }
1797 
drv_alterTableName(KDbTableSchema * tableSchema,const QString & newName)1798 bool KDbConnection::drv_alterTableName(KDbTableSchema* tableSchema, const QString& newName)
1799 {
1800     const QString oldTableName = tableSchema->name();
1801     tableSchema->setName(newName);
1802 
1803     if (!executeSql(KDbEscapedString("ALTER TABLE %1 RENAME TO %2")
1804                     .arg(KDbEscapedString(escapeIdentifier(oldTableName)),
1805                          KDbEscapedString(escapeIdentifier(newName)))))
1806     {
1807         tableSchema->setName(oldTableName); //restore old name
1808         return false;
1809     }
1810     return true;
1811 }
1812 
dropQuery(KDbQuerySchema * querySchema)1813 bool KDbConnection::dropQuery(KDbQuerySchema* querySchema)
1814 {
1815     clearResult();
1816     if (!querySchema)
1817         return false;
1818 
1819     KDbTransactionGuard tg;
1820     if (!beginAutoCommitTransaction(&tg))
1821         return false;
1822 
1823     //remove query schema from kexi__objects table
1824     if (!removeObject(querySchema->id())) {
1825         return false;
1826     }
1827 
1828 //! @todo update any structure that depend on this table!
1829     d->removeQuery(querySchema);
1830     return commitAutoCommitTransaction(tg.transaction());
1831 }
1832 
dropQuery(const QString & queryName)1833 bool KDbConnection::dropQuery(const QString& queryName)
1834 {
1835     clearResult();
1836     KDbQuerySchema* qs = querySchema(queryName);
1837     if (!qs) {
1838         m_result = KDbResult(ERR_OBJECT_NOT_FOUND,
1839                              tr("Query \"%1\" does not exist.").arg(queryName));
1840         return false;
1841     }
1842     return dropQuery(qs);
1843 }
1844 
drv_getTableNames(bool * ok)1845 QStringList KDbConnection::drv_getTableNames(bool *ok)
1846 {
1847     Q_ASSERT(ok);
1848     QStringList tableNames;
1849     const KDbEscapedString sql(d->driver->behavior()->GET_TABLE_NAMES_SQL);
1850     if (sql.isEmpty()) {
1851         *ok = false;
1852         return QStringList();
1853     }
1854     QSharedPointer<KDbSqlResult> result = prepareSql(sql);
1855     if (!result) {
1856         *ok = false;
1857         return QStringList();
1858     }
1859     Q_FOREVER {
1860         QSharedPointer<KDbSqlRecord> record = result->fetchRecord();
1861         if (!record) {
1862             if (result->lastResult().isError()) {
1863                 *ok = false;
1864                 return QStringList();
1865             }
1866             break;
1867         }
1868         tableNames.append(record->stringValue(0));
1869     }
1870     *ok = true;
1871     return tableNames;
1872 }
1873 
drv_createTable(const KDbTableSchema & tableSchema)1874 bool KDbConnection::drv_createTable(const KDbTableSchema& tableSchema)
1875 {
1876     const KDbNativeStatementBuilder builder(this, KDb::DriverEscaping);
1877     KDbEscapedString sql;
1878     if (!builder.generateCreateTableStatement(&sql,tableSchema)) {
1879         return false;
1880     }
1881     //kdbDebug() << "******** " << sql;
1882     return executeSql(sql);
1883 }
1884 
drv_createTable(const QString & tableName)1885 bool KDbConnection::drv_createTable(const QString& tableName)
1886 {
1887     KDbTableSchema *ts = tableSchema(tableName);
1888     if (!ts)
1889         return false;
1890     return drv_createTable(*ts);
1891 }
1892 
beginAutoCommitTransaction(KDbTransactionGuard * tg)1893 bool KDbConnection::beginAutoCommitTransaction(KDbTransactionGuard* tg)
1894 {
1895     if ((d->driver->behavior()->features & KDbDriver::IgnoreTransactions)
1896             || !d->autoCommit) {
1897         tg->setTransaction(KDbTransaction());
1898         return true;
1899     }
1900 
1901     // commit current transaction (if present) for drivers
1902     // that allow single transaction per connection
1903     if (d->driver->behavior()->features & KDbDriver::SingleTransactions) {
1904         if (d->defaultTransactionStartedInside) //only commit internally started transaction
1905             if (!commitTransaction(d->default_trans, KDbTransaction::CommitOption::IgnoreInactive)) {
1906                 tg->setTransaction(KDbTransaction());
1907                 return false; //we have a real error
1908             }
1909 
1910         d->defaultTransactionStartedInside = d->default_trans.isNull();
1911         if (!d->defaultTransactionStartedInside) {
1912             tg->setTransaction(d->default_trans);
1913             tg->doNothing();
1914             return true; //reuse externally started transaction
1915         }
1916     } else if (!(d->driver->behavior()->features & KDbDriver::MultipleTransactions)) {
1917         tg->setTransaction(KDbTransaction());
1918         return true; //no trans. supported at all - just return
1919     }
1920     tg->setTransaction(beginTransaction());
1921     return !m_result.isError();
1922 }
1923 
commitAutoCommitTransaction(const KDbTransaction & trans)1924 bool KDbConnection::commitAutoCommitTransaction(const KDbTransaction& trans)
1925 {
1926     if (d->driver->behavior()->features & KDbDriver::IgnoreTransactions)
1927         return true;
1928     if (trans.isNull() || !d->driver->transactionsSupported())
1929         return true;
1930     if (d->driver->behavior()->features & KDbDriver::SingleTransactions) {
1931         if (!d->defaultTransactionStartedInside) //only commit internally started transaction
1932             return true; //give up
1933     }
1934     return commitTransaction(trans, KDbTransaction::CommitOption::IgnoreInactive);
1935 }
1936 
rollbackAutoCommitTransaction(const KDbTransaction & trans)1937 bool KDbConnection::rollbackAutoCommitTransaction(const KDbTransaction& trans)
1938 {
1939     if (trans.isNull() || !d->driver->transactionsSupported())
1940         return true;
1941     return rollbackTransaction(trans);
1942 }
1943 
1944 #define SET_ERR_TRANS_NOT_SUPP \
1945     { m_result = KDbResult(ERR_UNSUPPORTED_DRV_FEATURE, \
1946                            KDbConnection::tr("Transactions are not supported for \"%1\" driver.").arg( d->driver->metaData()->name() )); }
1947 
1948 #define SET_BEGIN_TR_ERROR \
1949     { if (!m_result.isError()) \
1950             m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION, \
1951                                  KDbConnection::tr("Begin transaction failed.")); }
1952 
beginTransaction()1953 KDbTransaction KDbConnection::beginTransaction()
1954 {
1955     if (!checkIsDatabaseUsed())
1956         return KDbTransaction();
1957     KDbTransaction trans;
1958     if (d->driver->behavior()->features & KDbDriver::IgnoreTransactions) {
1959         //we're creating dummy transaction data here,
1960         //so it will look like active
1961         trans.m_data = new KDbTransactionData(this);
1962         d->transactions.append(trans);
1963         return trans;
1964     }
1965     if (d->driver->behavior()->features & KDbDriver::SingleTransactions) {
1966         if (d->default_trans.isActive()) {
1967             m_result = KDbResult(ERR_TRANSACTION_ACTIVE,
1968                                  tr("Transaction already started."));
1969             return KDbTransaction();
1970         }
1971         if (!(trans.m_data = drv_beginTransaction())) {
1972             SET_BEGIN_TR_ERROR;
1973             return KDbTransaction();
1974         }
1975         d->default_trans = trans;
1976         d->transactions.append(trans);
1977         return d->default_trans;
1978     }
1979     if (d->driver->behavior()->features & KDbDriver::MultipleTransactions) {
1980         if (!(trans.m_data = drv_beginTransaction())) {
1981             SET_BEGIN_TR_ERROR;
1982             return KDbTransaction();
1983         }
1984         d->transactions.append(trans);
1985         return trans;
1986     }
1987 
1988     SET_ERR_TRANS_NOT_SUPP;
1989     return KDbTransaction();
1990 }
1991 
commitTransaction(const KDbTransaction trans,KDbTransaction::CommitOptions options)1992 bool KDbConnection::commitTransaction(const KDbTransaction trans,
1993                                       KDbTransaction::CommitOptions options)
1994 {
1995     if (!isDatabaseUsed())
1996         return false;
1997     if (!d->driver->transactionsSupported()
1998             && !(d->driver->behavior()->features & KDbDriver::IgnoreTransactions)) {
1999         SET_ERR_TRANS_NOT_SUPP;
2000         return false;
2001     }
2002     KDbTransaction t = trans;
2003     if (!t.isActive()) { //try default tr.
2004         if (!d->default_trans.isActive()) {
2005             if (options & KDbTransaction::CommitOption::IgnoreInactive) {
2006                 return true;
2007             }
2008             clearResult();
2009             m_result = KDbResult(ERR_NO_TRANSACTION_ACTIVE,
2010                                  tr("Transaction not started."));
2011             return false;
2012         }
2013         t = d->default_trans;
2014         d->default_trans = KDbTransaction(); //now: no default tr.
2015     }
2016     bool ret = true;
2017     if (!(d->driver->behavior()->features & KDbDriver::IgnoreTransactions))
2018         ret = drv_commitTransaction(t.m_data);
2019     if (t.m_data)
2020         t.m_data->setActive(false); //now this transaction if inactive
2021     if (!d->dontRemoveTransactions) //true=transaction obj will be later removed from list
2022         d->transactions.removeAt(d->transactions.indexOf(t));
2023     if (!ret && !m_result.isError())
2024         m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION,
2025                              tr("Error on commit transaction."));
2026     return ret;
2027 }
2028 
rollbackTransaction(const KDbTransaction trans,KDbTransaction::CommitOptions options)2029 bool KDbConnection::rollbackTransaction(const KDbTransaction trans,
2030                                         KDbTransaction::CommitOptions options)
2031 {
2032     if (!isDatabaseUsed())
2033         return false;
2034     if (!d->driver->transactionsSupported()
2035             && !(d->driver->behavior()->features & KDbDriver::IgnoreTransactions)) {
2036         SET_ERR_TRANS_NOT_SUPP;
2037         return false;
2038     }
2039     KDbTransaction t = trans;
2040     if (!t.isActive()) { //try default tr.
2041         if (!d->default_trans.isActive()) {
2042             if (options & KDbTransaction::CommitOption::IgnoreInactive) {
2043                 return true;
2044             }
2045             clearResult();
2046             m_result = KDbResult(ERR_NO_TRANSACTION_ACTIVE,
2047                                  tr("Transaction not started."));
2048             return false;
2049         }
2050         t = d->default_trans;
2051         d->default_trans = KDbTransaction(); //now: no default tr.
2052     }
2053     bool ret = true;
2054     if (!(d->driver->behavior()->features & KDbDriver::IgnoreTransactions))
2055         ret = drv_rollbackTransaction(t.m_data);
2056     if (t.m_data)
2057         t.m_data->setActive(false); //now this transaction if inactive
2058     if (!d->dontRemoveTransactions) //true=transaction obj will be later removed from list
2059         d->transactions.removeAt(d->transactions.indexOf(t));
2060     if (!ret && !m_result.isError())
2061         m_result = KDbResult(ERR_ROLLBACK_OR_COMMIT_TRANSACTION,
2062                              tr("Error on rollback transaction."));
2063     return ret;
2064 }
2065 
2066 #undef SET_ERR_TRANS_NOT_SUPP
2067 #undef SET_BEGIN_TR_ERROR
2068 
2069 /*bool KDbConnection::duringTransaction()
2070 {
2071   return drv_duringTransaction();
2072 }*/
2073 
defaultTransaction() const2074 KDbTransaction KDbConnection::defaultTransaction() const
2075 {
2076     return d->default_trans;
2077 }
2078 
setDefaultTransaction(const KDbTransaction & trans)2079 void KDbConnection::setDefaultTransaction(const KDbTransaction& trans)
2080 {
2081     if (!isDatabaseUsed())
2082         return;
2083     if (!(d->driver->behavior()->features & KDbDriver::IgnoreTransactions)
2084             && (!trans.isActive() || !d->driver->transactionsSupported())) {
2085         return;
2086     }
2087     d->default_trans = trans;
2088 }
2089 
transactions()2090 QList<KDbTransaction> KDbConnection::transactions()
2091 {
2092     return d->transactions;
2093 }
2094 
autoCommit() const2095 bool KDbConnection::autoCommit() const
2096 {
2097     return d->autoCommit;
2098 }
2099 
setAutoCommit(bool on)2100 bool KDbConnection::setAutoCommit(bool on)
2101 {
2102     if (d->autoCommit == on || d->driver->behavior()->features & KDbDriver::IgnoreTransactions)
2103         return true;
2104     if (!drv_setAutoCommit(on))
2105         return false;
2106     d->autoCommit = on;
2107     return true;
2108 }
2109 
drv_beginTransaction()2110 KDbTransactionData* KDbConnection::drv_beginTransaction()
2111 {
2112     if (!executeSql(KDbEscapedString("BEGIN")))
2113         return nullptr;
2114     return new KDbTransactionData(this);
2115 }
2116 
drv_commitTransaction(KDbTransactionData *)2117 bool KDbConnection::drv_commitTransaction(KDbTransactionData *)
2118 {
2119     return executeSql(KDbEscapedString("COMMIT"));
2120 }
2121 
drv_rollbackTransaction(KDbTransactionData *)2122 bool KDbConnection::drv_rollbackTransaction(KDbTransactionData *)
2123 {
2124     return executeSql(KDbEscapedString("ROLLBACK"));
2125 }
2126 
drv_setAutoCommit(bool)2127 bool KDbConnection::drv_setAutoCommit(bool /*on*/)
2128 {
2129     return true;
2130 }
2131 
executeQuery(const KDbEscapedString & sql,KDbCursor::Options options)2132 KDbCursor* KDbConnection::executeQuery(const KDbEscapedString& sql, KDbCursor::Options options)
2133 {
2134     if (sql.isEmpty())
2135         return nullptr;
2136     KDbCursor *c = prepareQuery(sql, options);
2137     if (!c)
2138         return nullptr;
2139     if (!c->open()) {//err - kill that
2140         m_result = c->result();
2141         CursorDeleter deleter(c);
2142         return nullptr;
2143     }
2144     return c;
2145 }
2146 
executeQuery(KDbQuerySchema * query,const QList<QVariant> & params,KDbCursor::Options options)2147 KDbCursor* KDbConnection::executeQuery(KDbQuerySchema* query, const QList<QVariant>& params,
2148                                        KDbCursor::Options options)
2149 {
2150     KDbCursor *c = prepareQuery(query, params, options);
2151     if (!c)
2152         return nullptr;
2153     if (!c->open()) {//err - kill that
2154         m_result = c->result();
2155         CursorDeleter deleter(c);
2156         return nullptr;
2157     }
2158     return c;
2159 }
2160 
executeQuery(KDbQuerySchema * query,KDbCursor::Options options)2161 KDbCursor* KDbConnection::executeQuery(KDbQuerySchema* query, KDbCursor::Options options)
2162 {
2163     return executeQuery(query, QList<QVariant>(), options);
2164 }
2165 
executeQuery(KDbTableSchema * table,KDbCursor::Options options)2166 KDbCursor* KDbConnection::executeQuery(KDbTableSchema* table, KDbCursor::Options options)
2167 {
2168     return executeQuery(table->query(), options);
2169 }
2170 
prepareQuery(KDbTableSchema * table,KDbCursor::Options options)2171 KDbCursor* KDbConnection::prepareQuery(KDbTableSchema* table, KDbCursor::Options options)
2172 {
2173     return prepareQuery(table->query(), options);
2174 }
2175 
prepareQuery(KDbQuerySchema * query,const QList<QVariant> & params,KDbCursor::Options options)2176 KDbCursor* KDbConnection::prepareQuery(KDbQuerySchema* query, const QList<QVariant>& params,
2177                                        KDbCursor::Options options)
2178 {
2179     KDbCursor* cursor = prepareQuery(query, options);
2180     if (cursor)
2181         cursor->setQueryParameters(params);
2182     return cursor;
2183 }
2184 
deleteCursor(KDbCursor * cursor)2185 bool KDbConnection::deleteCursor(KDbCursor *cursor)
2186 {
2187     if (!cursor)
2188         return false;
2189     if (cursor->connection() != this) {//illegal call
2190         kdbWarning() << "Could not delete the cursor not owned by the same connection!";
2191         return false;
2192     }
2193     const bool ret = cursor->close();
2194     CursorDeleter deleter(cursor);
2195     return ret;
2196 }
2197 
2198 //! @todo IMPORTANT: fix KDbConnection::setupObjectData() after refactoring
setupObjectData(const KDbRecordData & data,KDbObject * object)2199 bool KDbConnection::setupObjectData(const KDbRecordData &data, KDbObject *object)
2200 {
2201     if (data.count() < 5) {
2202         kdbWarning() << "Aborting, object data should have at least 5 elements, found" << data.count();
2203         return false;
2204     }
2205     bool ok;
2206     const int id = data[0].toInt(&ok);
2207     if (!ok)
2208         return false;
2209     object->setId(id);
2210     const QString name(data[2].toString());
2211     if (!KDb::isIdentifier(name)) {
2212         m_result = KDbResult(ERR_INVALID_IDENTIFIER,
2213                              tr("Invalid object name \"%1\".").arg(name));
2214         return false;
2215     }
2216     object->setName(name);
2217     object->setCaption(data[3].toString());
2218     object->setDescription(data[4].toString());
2219 
2220 // kdbDebug()<<"@@@ KDbConnection::setupObjectData() == " << sdata.schemaDataDebugString();
2221     return true;
2222 }
2223 
loadObjectData(int type,int id,KDbObject * object)2224 tristate KDbConnection::loadObjectData(int type, int id, KDbObject* object)
2225 {
2226     KDbRecordData data;
2227     if (type == KDb::AnyObjectType) {
2228         if (true != querySingleRecord(KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, "
2229                                                        "o_desc FROM kexi__objects WHERE o_id=%1")
2230                                           .arg(d->driver->valueToSql(KDbField::Integer, id)),
2231                                       &data)) {
2232             return cancelled;
2233         }
2234     } else {
2235         if (true != querySingleRecord(KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc "
2236                                                        "FROM kexi__objects WHERE o_type=%1 AND o_id=%2")
2237                                           .arg(d->driver->valueToSql(KDbField::Integer, type))
2238                                           .arg(d->driver->valueToSql(KDbField::Integer, id)),
2239                                       &data))
2240         {
2241             return cancelled;
2242         }
2243     }
2244     return setupObjectData(data, object);
2245 }
2246 
loadObjectData(int type,const QString & name,KDbObject * object)2247 tristate KDbConnection::loadObjectData(int type, const QString& name, KDbObject* object)
2248 {
2249     KDbRecordData data;
2250     if (true != querySingleRecord(
2251             KDbEscapedString("SELECT o_id, o_type, o_name, o_caption, o_desc "
2252                           "FROM kexi__objects WHERE o_type=%1 AND o_name=%2")
2253                           .arg(d->driver->valueToSql(KDbField::Integer, type))
2254                           .arg(escapeString(name)),
2255             &data))
2256     {
2257         return cancelled;
2258     }
2259     return setupObjectData(data, object);
2260 }
2261 
storeObjectDataInternal(KDbObject * object,bool newObject)2262 bool KDbConnection::storeObjectDataInternal(KDbObject* object, bool newObject)
2263 {
2264     KDbTableSchema *ts = d->table(QLatin1String("kexi__objects"));
2265     if (!ts)
2266         return false;
2267     if (newObject) {
2268         int existingID;
2269         if (true == querySingleNumber(
2270                 KDbEscapedString("SELECT o_id FROM kexi__objects WHERE o_type=%1 AND o_name=%2")
2271                                  .arg(d->driver->valueToSql(KDbField::Integer, object->type()))
2272                                  .arg(escapeString(object->name())), &existingID))
2273         {
2274             //we already have stored an object data with the same name and type:
2275             //just update it's properties as it would be existing object
2276             object->setId(existingID);
2277             newObject = false;
2278         }
2279     }
2280     if (newObject) {
2281         if (object->id() <= 0) {//get new ID
2282             QScopedPointer<KDbFieldList> fl(ts->subList(
2283                 QList<QByteArray>() << "o_type" << "o_name" << "o_caption" << "o_desc"));
2284             if (!fl) {
2285                 return false;
2286             }
2287             QSharedPointer<KDbSqlResult> result
2288                 = insertRecord(fl.data(), QVariant(object->type()), QVariant(object->name()),
2289                                QVariant(object->caption()), QVariant(object->description()));
2290             if (!result) {
2291                 return false;
2292             }
2293             //fetch newly assigned ID
2294 //! @todo safe to cast it?
2295             quint64 obj_id = KDb::lastInsertedAutoIncValue(result, QLatin1String("o_id"), *ts);
2296             //kdbDebug() << "NEW obj_id == " << obj_id;
2297             if (obj_id == std::numeric_limits<quint64>::max()) {
2298                 return false;
2299             }
2300             object->setId(obj_id);
2301             return true;
2302         } else {
2303             QScopedPointer<KDbFieldList> fl(ts->subList(
2304                 QList<QByteArray>() << "o_id" << "o_type" << "o_name" << "o_caption" << "o_desc"));
2305             return fl && insertRecord(fl.data(), QVariant(object->id()), QVariant(object->type()),
2306                                       QVariant(object->name()), QVariant(object->caption()),
2307                                       QVariant(object->description()));
2308         }
2309     }
2310     //existing object:
2311     return executeSql(
2312                KDbEscapedString("UPDATE kexi__objects SET o_type=%2, o_caption=%3, o_desc=%4 WHERE o_id=%1")
2313                .arg(d->driver->valueToSql(KDbField::Integer, object->id()))
2314                .arg(d->driver->valueToSql(KDbField::Integer, object->type()))
2315                .arg(escapeString(object->caption()))
2316                .arg(escapeString(object->description())));
2317 }
2318 
storeObjectData(KDbObject * object)2319 bool KDbConnection::storeObjectData(KDbObject* object)
2320 {
2321     return storeObjectDataInternal(object, false);
2322 }
2323 
storeNewObjectData(KDbObject * object)2324 bool KDbConnection::storeNewObjectData(KDbObject* object)
2325 {
2326     return storeObjectDataInternal(object, true);
2327 }
2328 
escapeIdentifier(const QString & id,KDb::IdentifierEscapingType escapingType) const2329 QString KDbConnection::escapeIdentifier(const QString& id, KDb::IdentifierEscapingType escapingType) const
2330 {
2331     return escapingType == KDb::KDbEscaping
2332         ? KDb::escapeIdentifier(id) : escapeIdentifier(id);
2333 }
2334 
executeQueryInternal(const KDbEscapedString & sql,KDbQuerySchema * query,const QList<QVariant> * params)2335 KDbCursor* KDbConnection::executeQueryInternal(const KDbEscapedString& sql,
2336                                                KDbQuerySchema* query,
2337                                                const QList<QVariant>* params)
2338 {
2339     Q_ASSERT(!sql.isEmpty() || query);
2340     clearResult();
2341     if (!sql.isEmpty()) {
2342         return executeQuery(sql);
2343     }
2344     if (!query) {
2345         return nullptr;
2346     }
2347     if (params) {
2348         return executeQuery(query, *params);
2349     }
2350     return executeQuery(query);
2351 }
2352 
querySingleRecordInternal(KDbRecordData * data,const KDbEscapedString * sql,KDbQuerySchema * query,const QList<QVariant> * params,QueryRecordOptions options)2353 tristate KDbConnection::querySingleRecordInternal(KDbRecordData *data, const KDbEscapedString *sql,
2354                                                   KDbQuerySchema *query,
2355                                                   const QList<QVariant> *params,
2356                                                   QueryRecordOptions options)
2357 {
2358     Q_ASSERT(sql || query);
2359     if (sql) {
2360         //! @todo does not work with non-SQL data sources
2361         m_result.setSql(d->driver->addLimitTo1(*sql, options & QueryRecordOption::AddLimitTo1));
2362     }
2363     KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params);
2364     if (!cursor) {
2365         kdbWarning() << "!querySingleRecordInternal() " << m_result.sql();
2366         return false;
2367     }
2368     if (!cursor->moveFirst() || cursor->eof() || !cursor->storeCurrentRecord(data)) {
2369         const tristate result = cursor->result().isError() ? tristate(false) : tristate(cancelled);
2370         // kdbDebug() << "!cursor->moveFirst() || cursor->eof() || cursor->storeCurrentRecord(data)
2371         // "
2372         //          "m_result.sql()=" << m_result.sql();
2373         m_result = cursor->result();
2374         deleteCursor(cursor);
2375         return result;
2376     }
2377     return deleteCursor(cursor);
2378 }
2379 
querySingleRecord(const KDbEscapedString & sql,KDbRecordData * data,QueryRecordOptions options)2380 tristate KDbConnection::querySingleRecord(const KDbEscapedString &sql, KDbRecordData *data,
2381                                           QueryRecordOptions options)
2382 {
2383     return querySingleRecordInternal(data, &sql, nullptr, nullptr, options);
2384 }
2385 
querySingleRecord(KDbQuerySchema * query,KDbRecordData * data,QueryRecordOptions options)2386 tristate KDbConnection::querySingleRecord(KDbQuerySchema *query, KDbRecordData *data,
2387                                           QueryRecordOptions options)
2388 {
2389     return querySingleRecordInternal(data, nullptr, query, nullptr, options);
2390 }
2391 
querySingleRecord(KDbQuerySchema * query,KDbRecordData * data,const QList<QVariant> & params,QueryRecordOptions options)2392 tristate KDbConnection::querySingleRecord(KDbQuerySchema *query, KDbRecordData *data,
2393                                           const QList<QVariant> &params, QueryRecordOptions options)
2394 {
2395     return querySingleRecordInternal(data, nullptr, query, &params, options);
2396 }
2397 
checkIfColumnExists(KDbCursor * cursor,int column)2398 bool KDbConnection::checkIfColumnExists(KDbCursor *cursor, int column)
2399 {
2400     if (column >= cursor->fieldCount()) {
2401         m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING,
2402                              tr("Column \"%1\" does not exist in the query.").arg(column));
2403         return false;
2404     }
2405     return true;
2406 }
2407 
querySingleStringInternal(const KDbEscapedString * sql,QString * value,KDbQuerySchema * query,const QList<QVariant> * params,int column,QueryRecordOptions options)2408 tristate KDbConnection::querySingleStringInternal(const KDbEscapedString *sql, QString *value,
2409                                                   KDbQuerySchema *query,
2410                                                   const QList<QVariant> *params, int column,
2411                                                   QueryRecordOptions options)
2412 {
2413     Q_ASSERT(sql || query);
2414     if (sql) {
2415         //! @todo does not work with non-SQL data sources
2416         m_result.setSql(d->driver->addLimitTo1(*sql, options & QueryRecordOption::AddLimitTo1));
2417     }
2418     KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params);
2419     if (!cursor) {
2420         kdbWarning() << "!querySingleStringInternal()" << m_result.sql();
2421         return false;
2422     }
2423     if (!cursor->moveFirst() || cursor->eof()) {
2424         const tristate result = cursor->result().isError() ? tristate(false) : tristate(cancelled);
2425         // kdbDebug() << "!cursor->moveFirst() || cursor->eof()" << m_result.sql();
2426         deleteCursor(cursor);
2427         return result;
2428     }
2429     if (!checkIfColumnExists(cursor, column)) {
2430         deleteCursor(cursor);
2431         return false;
2432     }
2433     if (value) {
2434         *value = cursor->value(column).toString();
2435     }
2436     return deleteCursor(cursor);
2437 }
2438 
querySingleString(const KDbEscapedString & sql,QString * value,int column,QueryRecordOptions options)2439 tristate KDbConnection::querySingleString(const KDbEscapedString &sql, QString *value, int column,
2440                                           QueryRecordOptions options)
2441 {
2442     return querySingleStringInternal(&sql, value, nullptr, nullptr, column, options);
2443 }
2444 
querySingleString(KDbQuerySchema * query,QString * value,int column,QueryRecordOptions options)2445 tristate KDbConnection::querySingleString(KDbQuerySchema *query, QString *value, int column,
2446                                           QueryRecordOptions options)
2447 {
2448     return querySingleStringInternal(nullptr, value, query, nullptr, column, options);
2449 }
2450 
querySingleString(KDbQuerySchema * query,QString * value,const QList<QVariant> & params,int column,QueryRecordOptions options)2451 tristate KDbConnection::querySingleString(KDbQuerySchema *query, QString *value,
2452                                           const QList<QVariant> &params, int column,
2453                                           QueryRecordOptions options)
2454 {
2455     return querySingleStringInternal(nullptr, value, query, &params, column, options);
2456 }
2457 
querySingleNumberInternal(const KDbEscapedString * sql,int * number,KDbQuerySchema * query,const QList<QVariant> * params,int column,QueryRecordOptions options)2458 tristate KDbConnection::querySingleNumberInternal(const KDbEscapedString *sql, int *number,
2459                                                   KDbQuerySchema *query,
2460                                                   const QList<QVariant> *params, int column,
2461                                                   QueryRecordOptions options)
2462 {
2463     QString str;
2464     const tristate result = querySingleStringInternal(sql, &str, query, params, column, options);
2465     if (result != true)
2466         return result;
2467     bool ok;
2468     const int _number = str.toInt(&ok);
2469     if (!ok)
2470         return false;
2471     if (number) {
2472         *number = _number;
2473     }
2474     return true;
2475 }
2476 
querySingleNumber(const KDbEscapedString & sql,int * number,int column,QueryRecordOptions options)2477 tristate KDbConnection::querySingleNumber(const KDbEscapedString &sql, int *number, int column,
2478                                           QueryRecordOptions options)
2479 {
2480     return querySingleNumberInternal(&sql, number, nullptr, nullptr, column, options);
2481 }
2482 
querySingleNumber(KDbQuerySchema * query,int * number,int column,QueryRecordOptions options)2483 tristate KDbConnection::querySingleNumber(KDbQuerySchema *query, int *number, int column,
2484                                           QueryRecordOptions options)
2485 {
2486     return querySingleNumberInternal(nullptr, number, query, nullptr, column, options);
2487 }
2488 
querySingleNumber(KDbQuerySchema * query,int * number,const QList<QVariant> & params,int column,QueryRecordOptions options)2489 tristate KDbConnection::querySingleNumber(KDbQuerySchema *query, int *number,
2490                                           const QList<QVariant> &params, int column,
2491                                           QueryRecordOptions options)
2492 {
2493     return querySingleNumberInternal(nullptr, number, query, &params, column, options);
2494 }
2495 
queryStringListInternal(const KDbEscapedString * sql,QStringList * list,KDbQuerySchema * query,const QList<QVariant> * params,int column,bool (* filterFunction)(const QString &))2496 bool KDbConnection::queryStringListInternal(const KDbEscapedString *sql, QStringList *list,
2497                                             KDbQuerySchema *query, const QList<QVariant> *params,
2498                                             int column, bool (*filterFunction)(const QString &))
2499 {
2500     if (sql) {
2501         m_result.setSql(*sql);
2502     }
2503     KDbCursor *cursor = executeQueryInternal(m_result.sql(), query, params);
2504     if (!cursor) {
2505         kdbWarning() << "!queryStringListInternal() " << m_result.sql();
2506         return false;
2507     }
2508     cursor->moveFirst();
2509     if (cursor->result().isError()) {
2510         m_result = cursor->result();
2511         deleteCursor(cursor);
2512         return false;
2513     }
2514     if (!cursor->eof() && !checkIfColumnExists(cursor, column)) {
2515         deleteCursor(cursor);
2516         return false;
2517     }
2518     if (list) {
2519         list->clear();
2520     }
2521     QStringList listResult;
2522     while (!cursor->eof()) {
2523         const QString str(cursor->value(column).toString());
2524         if (!filterFunction || filterFunction(str)) {
2525             listResult.append(str);
2526         }
2527         if (!cursor->moveNext() && cursor->result().isError()) {
2528             m_result = cursor->result();
2529             deleteCursor(cursor);
2530             return false;
2531         }
2532     }
2533     if (list) {
2534         *list = listResult;
2535     }
2536     return deleteCursor(cursor);
2537 }
2538 
queryStringList(const KDbEscapedString & sql,QStringList * list,int column)2539 bool KDbConnection::queryStringList(const KDbEscapedString& sql, QStringList* list,
2540                                     int column)
2541 {
2542     return queryStringListInternal(&sql, list, nullptr, nullptr, column, nullptr);
2543 }
2544 
queryStringList(KDbQuerySchema * query,QStringList * list,int column)2545 bool KDbConnection::queryStringList(KDbQuerySchema* query, QStringList* list, int column)
2546 {
2547     return queryStringListInternal(nullptr, list, query, nullptr, column, nullptr);
2548 }
2549 
queryStringList(KDbQuerySchema * query,QStringList * list,const QList<QVariant> & params,int column)2550 bool KDbConnection::queryStringList(KDbQuerySchema* query, QStringList* list,
2551                                     const QList<QVariant>& params, int column)
2552 {
2553     return queryStringListInternal(nullptr, list, query, &params, column, nullptr);
2554 }
2555 
resultExists(const KDbEscapedString & sql,QueryRecordOptions options)2556 tristate KDbConnection::resultExists(const KDbEscapedString &sql, QueryRecordOptions options)
2557 {
2558     // optimization
2559     if (d->driver->behavior()->SELECT_1_SUBQUERY_SUPPORTED) {
2560         // this is at least for sqlite
2561         if ((options & QueryRecordOption::AddLimitTo1) && sql.left(6).toUpper() == "SELECT") {
2562             m_result.setSql(d->driver->addLimitTo1("SELECT 1 FROM (" + sql + ')'));
2563         } else {
2564             m_result.setSql(sql);
2565         }
2566     } else {
2567         if ((options & QueryRecordOption::AddLimitTo1) && sql.startsWith("SELECT")) {
2568             m_result.setSql(d->driver->addLimitTo1(sql));
2569         } else {
2570             m_result.setSql(sql);
2571         }
2572     }
2573     KDbCursor *cursor = executeQuery(m_result.sql());
2574     if (!cursor) {
2575         kdbWarning() << "!executeQuery()" << m_result.sql();
2576         return cancelled;
2577     }
2578     if (!cursor->moveFirst() || cursor->eof()) {
2579         //kdbWarning() << "!cursor->moveFirst() || cursor->eof()" << m_result.sql();
2580         m_result = cursor->result();
2581         deleteCursor(cursor);
2582         return m_result.isError() ? cancelled : tristate(false);
2583     }
2584     return deleteCursor(cursor) ? tristate(true) : cancelled;
2585 }
2586 
isEmpty(KDbTableSchema * table)2587 tristate KDbConnection::isEmpty(KDbTableSchema* table)
2588 {
2589     const KDbNativeStatementBuilder builder(this, KDb::DriverEscaping);
2590     KDbEscapedString sql;
2591     if (!builder.generateSelectStatement(&sql, table)) {
2592         return cancelled;
2593     }
2594     const tristate result = resultExists(sql);
2595     if (~result) {
2596         return cancelled;
2597     }
2598     return result == false;
2599 }
2600 
2601 //! Used by addFieldPropertyToExtendedTableSchemaData()
createExtendedTableSchemaMainElementIfNeeded(QDomDocument * doc,QDomElement * extendedTableSchemaMainEl,bool * extendedTableSchemaStringIsEmpty)2602 static void createExtendedTableSchemaMainElementIfNeeded(
2603     QDomDocument* doc, QDomElement* extendedTableSchemaMainEl,
2604     bool* extendedTableSchemaStringIsEmpty)
2605 {
2606     if (!*extendedTableSchemaStringIsEmpty)
2607         return;
2608     //init document
2609     *extendedTableSchemaMainEl = doc->createElement(QLatin1String("EXTENDED_TABLE_SCHEMA"));
2610     doc->appendChild(*extendedTableSchemaMainEl);
2611     extendedTableSchemaMainEl->setAttribute(QLatin1String("version"),
2612                                             QString::number(KDB_EXTENDED_TABLE_SCHEMA_VERSION));
2613     *extendedTableSchemaStringIsEmpty = false;
2614 }
2615 
2616 //! Used by addFieldPropertyToExtendedTableSchemaData()
createExtendedTableSchemaFieldElementIfNeeded(QDomDocument * doc,QDomElement * extendedTableSchemaMainEl,const QString & fieldName,QDomElement * extendedTableSchemaFieldEl,bool append=true)2617 static void createExtendedTableSchemaFieldElementIfNeeded(QDomDocument* doc,
2618         QDomElement* extendedTableSchemaMainEl, const QString& fieldName, QDomElement* extendedTableSchemaFieldEl,
2619         bool append = true)
2620 {
2621     if (!extendedTableSchemaFieldEl->isNull())
2622         return;
2623     *extendedTableSchemaFieldEl = doc->createElement(QLatin1String("field"));
2624     if (append)
2625         extendedTableSchemaMainEl->appendChild(*extendedTableSchemaFieldEl);
2626     extendedTableSchemaFieldEl->setAttribute(QLatin1String("name"), fieldName);
2627 }
2628 
2629 /*! @internal used by storeExtendedTableSchemaData()
2630  Creates DOM node for @a propertyName and @a propertyValue.
2631  Creates enclosing EXTENDED_TABLE_SCHEMA element if EXTENDED_TABLE_SCHEMA is true.
2632  Updates extendedTableSchemaStringIsEmpty and extendedTableSchemaMainEl afterwards.
2633  If extendedTableSchemaFieldEl is null, creates <field> element (with optional
2634  "custom" attribute is @a custom is false). */
addFieldPropertyToExtendedTableSchemaData(const KDbField & f,const QByteArray & propertyName,const QVariant & propertyValue,QDomDocument * doc,QDomElement * extendedTableSchemaMainEl,QDomElement * extendedTableSchemaFieldEl,bool * extendedTableSchemaStringIsEmpty,bool custom=false)2635 static void addFieldPropertyToExtendedTableSchemaData(
2636     const KDbField& f, const QByteArray &propertyName, const QVariant& propertyValue,
2637     QDomDocument* doc, QDomElement* extendedTableSchemaMainEl,
2638     QDomElement* extendedTableSchemaFieldEl,
2639     bool* extendedTableSchemaStringIsEmpty,
2640     bool custom = false)
2641 {
2642     createExtendedTableSchemaMainElementIfNeeded(doc,
2643             extendedTableSchemaMainEl, extendedTableSchemaStringIsEmpty);
2644     createExtendedTableSchemaFieldElementIfNeeded(
2645         doc, extendedTableSchemaMainEl, f.name(), extendedTableSchemaFieldEl);
2646 
2647     //create <property>
2648     QDomElement extendedTableSchemaFieldPropertyEl = doc->createElement(QLatin1String("property"));
2649     extendedTableSchemaFieldEl->appendChild(extendedTableSchemaFieldPropertyEl);
2650     if (custom)
2651         extendedTableSchemaFieldPropertyEl.setAttribute(QLatin1String("custom"), QLatin1String("true"));
2652     extendedTableSchemaFieldPropertyEl.setAttribute(QLatin1String("name"), QLatin1String(propertyName));
2653     QDomElement extendedTableSchemaFieldPropertyValueEl;
2654     switch (propertyValue.type()) {
2655     case QVariant::String:
2656         extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("string"));
2657         break;
2658     case QVariant::ByteArray:
2659         extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("cstring"));
2660         break;
2661     case QVariant::Int:
2662     case QVariant::Double:
2663     case QVariant::UInt:
2664     case QVariant::LongLong:
2665     case QVariant::ULongLong:
2666         extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("number"));
2667         break;
2668     case QVariant::Bool:
2669         extendedTableSchemaFieldPropertyValueEl = doc->createElement(QLatin1String("bool"));
2670         break;
2671     default:
2672 //! @todo add more QVariant types
2673         kdbCritical() << "addFieldPropertyToExtendedTableSchemaData(): impl. error";
2674     }
2675     extendedTableSchemaFieldPropertyEl.appendChild(extendedTableSchemaFieldPropertyValueEl);
2676     extendedTableSchemaFieldPropertyValueEl.appendChild(
2677         doc->createTextNode(propertyValue.toString()));
2678 }
2679 
storeExtendedTableSchemaData(KDbTableSchema * tableSchema)2680 bool KDbConnection::storeExtendedTableSchemaData(KDbTableSchema* tableSchema)
2681 {
2682 //! @todo future: save in older versions if neeed
2683     QDomDocument doc(QLatin1String("EXTENDED_TABLE_SCHEMA"));
2684     QDomElement extendedTableSchemaMainEl;
2685     bool extendedTableSchemaStringIsEmpty = true;
2686 
2687     //for each field:
2688     foreach(KDbField* f, *tableSchema->fields()) {
2689         QDomElement extendedTableSchemaFieldEl;
2690         const KDbField::Type type = f->type(); // cache: evaluating type of expressions can be expensive
2691         if (f->visibleDecimalPlaces() >= 0/*nondefault*/ && KDb::supportsVisibleDecimalPlacesProperty(type)) {
2692             addFieldPropertyToExtendedTableSchemaData(
2693                 *f, "visibleDecimalPlaces", f->visibleDecimalPlaces(), &doc,
2694                 &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl,
2695                 &extendedTableSchemaStringIsEmpty);
2696         }
2697         if (type == KDbField::Text) {
2698             if (f->maxLengthStrategy() == KDbField::DefaultMaxLength) {
2699                 addFieldPropertyToExtendedTableSchemaData(
2700                     *f, "maxLengthIsDefault", true, &doc,
2701                     &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl,
2702                     &extendedTableSchemaStringIsEmpty);
2703             }
2704         }
2705 
2706         // boolean field with "not null"
2707 
2708         // add custom properties
2709         const KDbField::CustomPropertiesMap customProperties(f->customProperties());
2710         for (KDbField::CustomPropertiesMap::ConstIterator itCustom = customProperties.constBegin();
2711                 itCustom != customProperties.constEnd(); ++itCustom) {
2712             addFieldPropertyToExtendedTableSchemaData(
2713                 *f, itCustom.key(), itCustom.value(), &doc,
2714                 &extendedTableSchemaMainEl, &extendedTableSchemaFieldEl, &extendedTableSchemaStringIsEmpty,
2715                 /*custom*/true);
2716         }
2717         // save lookup table specification, if present
2718         KDbLookupFieldSchema *lookupFieldSchema = tableSchema->lookupFieldSchema(*f);
2719         if (lookupFieldSchema) {
2720             createExtendedTableSchemaFieldElementIfNeeded(
2721                 &doc, &extendedTableSchemaMainEl, f->name(), &extendedTableSchemaFieldEl, false/* !append */);
2722             lookupFieldSchema->saveToDom(&doc, &extendedTableSchemaFieldEl);
2723 
2724             if (extendedTableSchemaFieldEl.hasChildNodes()) {
2725                 // this element provides the definition, so let's append it now
2726                 createExtendedTableSchemaMainElementIfNeeded(&doc, &extendedTableSchemaMainEl,
2727                         &extendedTableSchemaStringIsEmpty);
2728                 extendedTableSchemaMainEl.appendChild(extendedTableSchemaFieldEl);
2729             }
2730         }
2731     }
2732 
2733     // Store extended schema information (see ExtendedTableSchemaInformation in Kexi Wiki)
2734     if (extendedTableSchemaStringIsEmpty) {
2735 #ifdef KDB_DEBUG_GUI
2736         KDb::alterTableActionDebugGUI(QLatin1String("** Extended table schema REMOVED."));
2737 #endif
2738         if (!removeDataBlock(tableSchema->id(), QLatin1String("extended_schema")))
2739             return false;
2740     } else {
2741 #ifdef KDB_DEBUG_GUI
2742         KDb::alterTableActionDebugGUI(
2743                     QLatin1String("** Extended table schema set to:\n") + doc.toString(4));
2744 #endif
2745         if (!storeDataBlock(tableSchema->id(), doc.toString(1), QLatin1String("extended_schema")))
2746             return false;
2747     }
2748     return true;
2749 }
2750 
loadExtendedTableSchemaData(KDbTableSchema * tableSchema)2751 bool KDbConnection::loadExtendedTableSchemaData(KDbTableSchema* tableSchema)
2752 {
2753 #define loadExtendedTableSchemaData_ERR \
2754     { m_result = KDbResult(tr("Error while loading extended table schema.", \
2755                               "Extended schema for a table: loading error")); \
2756       return false; }
2757 #define loadExtendedTableSchemaData_ERR2(details) \
2758     { m_result = KDbResult(details); \
2759       m_result.setMessageTitle(tr("Error while loading extended table schema.", \
2760                                   "Extended schema for a table: loading error")); \
2761       return false; }
2762 #define loadExtendedTableSchemaData_ERR3(data) \
2763     { m_result = KDbResult(tr("Invalid XML data: %1").arg(data.left(1024))); \
2764       m_result.setMessageTitle(tr("Error while loading extended table schema.", \
2765                                   "Extended schema for a table: loading error")); \
2766       return false; }
2767 
2768     // Load extended schema information, if present (see ExtendedTableSchemaInformation in Kexi Wiki)
2769     QString extendedTableSchemaString;
2770     tristate res = loadDataBlock(tableSchema->id(),
2771                                  &extendedTableSchemaString, QLatin1String("extended_schema"));
2772     if (!res)
2773         loadExtendedTableSchemaData_ERR;
2774     // extendedTableSchemaString will be just empty if there is no such data block
2775 
2776     if (extendedTableSchemaString.isEmpty())
2777         return true;
2778 
2779     QDomDocument doc;
2780     QString errorMsg;
2781     int errorLine, errorColumn;
2782     if (!doc.setContent(extendedTableSchemaString, &errorMsg, &errorLine, &errorColumn)) {
2783         loadExtendedTableSchemaData_ERR2(
2784             tr("Error in XML data: \"%1\" in line %2, column %3.\nXML data: %4")
2785                .arg(errorMsg).arg(errorLine).arg(errorColumn).arg(extendedTableSchemaString.left(1024)));
2786     }
2787 
2788 //! @todo look at the current format version (KDB_EXTENDED_TABLE_SCHEMA_VERSION)
2789 
2790     if (doc.doctype().name() != QLatin1String("EXTENDED_TABLE_SCHEMA"))
2791         loadExtendedTableSchemaData_ERR3(extendedTableSchemaString);
2792 
2793     QDomElement docEl = doc.documentElement();
2794     if (docEl.tagName() != QLatin1String("EXTENDED_TABLE_SCHEMA"))
2795         loadExtendedTableSchemaData_ERR3(extendedTableSchemaString);
2796 
2797     for (QDomNode n = docEl.firstChild(); !n.isNull(); n = n.nextSibling()) {
2798         QDomElement fieldEl = n.toElement();
2799         if (fieldEl.tagName() == QLatin1String("field")) {
2800             KDbField *f = tableSchema->field(fieldEl.attribute(QLatin1String("name")));
2801             if (f) {
2802                 //set properties of the field:
2803 //! @todo more properties
2804                 for (QDomNode propNode = fieldEl.firstChild();
2805                      !propNode.isNull(); propNode = propNode.nextSibling())
2806                 {
2807                     const QDomElement propEl = propNode.toElement();
2808                     bool ok;
2809                     int intValue;
2810                     if (propEl.tagName() == QLatin1String("property")) {
2811                         QByteArray propertyName = propEl.attribute(QLatin1String("name")).toLatin1();
2812                         if (propEl.attribute(QLatin1String("custom")) == QLatin1String("true")) {
2813                             //custom property
2814                             const QVariant v(KDb::loadPropertyValueFromDom(propEl.firstChild(), &ok));
2815                             if (ok) {
2816                                 f->setCustomProperty(propertyName, v);
2817                             }
2818                         }
2819                         else if (propertyName == "visibleDecimalPlaces") {
2820                             if (KDb::supportsVisibleDecimalPlacesProperty(f->type())) {
2821                                 intValue = KDb::loadIntPropertyValueFromDom(propEl.firstChild(), &ok);
2822                                 if (ok)
2823                                     f->setVisibleDecimalPlaces(intValue);
2824                             }
2825                         }
2826                         else if (propertyName == "maxLengthIsDefault") {
2827                             if (f->type() == KDbField::Text) {
2828                                 const bool maxLengthIsDefault
2829                                     = KDb::loadPropertyValueFromDom(propEl.firstChild(), &ok).toBool();
2830                                 if (ok) {
2831                                     f->setMaxLengthStrategy(
2832                                         maxLengthIsDefault ? KDbField::DefaultMaxLength : KDbField::DefinedMaxLength);
2833                                 }
2834                             }
2835                         }
2836 //! @todo more properties...
2837                     } else if (propEl.tagName() == QLatin1String("lookup-column")) {
2838                         KDbLookupFieldSchema *lookupFieldSchema = KDbLookupFieldSchema::loadFromDom(propEl);
2839                         if (lookupFieldSchema) {
2840                             kdbDebug() << f->name() << *lookupFieldSchema;
2841                             tableSchema->setLookupFieldSchema(f->name(), lookupFieldSchema);
2842                         }
2843                     }
2844                 }
2845             } else {
2846                 kdbWarning() << "no such field:" << fieldEl.attribute(QLatin1String("name"))
2847                         << "in table:" << tableSchema->name();
2848             }
2849         }
2850     }
2851 
2852     return true;
2853 }
2854 
setupField(const KDbRecordData & data)2855 KDbField* KDbConnection::setupField(const KDbRecordData &data)
2856 {
2857     bool ok = true;
2858     int f_int_type = data.at(1).toInt(&ok);
2859     if (f_int_type <= KDbField::InvalidType || f_int_type > KDbField::LastType)
2860         ok = false;
2861     if (!ok)
2862         return nullptr;
2863     KDbField::Type f_type = (KDbField::Type)f_int_type;
2864     const int f_len = qMax(0, data.at(3).toInt(&ok)); // defined limit
2865     if (!ok) {
2866         return nullptr;
2867     }
2868 //! @todo load maxLengthStrategy info to see if the maxLength is the default
2869 
2870     int f_prec = data.at(4).toInt(&ok);
2871     if (!ok)
2872         return nullptr;
2873     KDbField::Constraints f_constr = (KDbField::Constraints)data.at(5).toInt(&ok);
2874     if (!ok)
2875         return nullptr;
2876     KDbField::Options f_opts = (KDbField::Options)data.at(6).toInt(&ok);
2877     if (!ok)
2878         return nullptr;
2879 
2880     QString name(data.at(2).toString());
2881     if (!KDb::isIdentifier(name)) {
2882         name = KDb::stringToIdentifier(name);
2883     }
2884 
2885     KDbField *f = new KDbField(
2886         name, f_type, f_constr, f_opts, f_len, f_prec);
2887 
2888     QVariant defaultVariant = data.at(7);
2889     if (defaultVariant.isValid()) {
2890         defaultVariant = KDb::stringToVariant(defaultVariant.toString(), KDbField::variantType(f_type), &ok);
2891         if (ok) {
2892             f->setDefaultValue(defaultVariant);
2893         } else {
2894             kdbWarning() << "problem with KDb::stringToVariant(" << defaultVariant << ')';
2895             ok = true; //problem with defaultValue is not critical
2896         }
2897     }
2898 
2899     f->setCaption(data.at(9).toString());
2900     f->setDescription(data.at(10).toString());
2901     return f;
2902 }
2903 
tableSchema(const QString & tableName)2904 KDbTableSchema* KDbConnection::tableSchema(const QString& tableName)
2905 {
2906     KDbTableSchema *t = d->table(tableName);
2907     if (t || tableName.isEmpty()) {
2908         return t;
2909     }
2910     //not found: retrieve schema
2911     QScopedPointer<KDbTableSchema> newTable(new KDbTableSchema);
2912     clearResult();
2913     if (true != loadObjectData(KDb::TableObjectType, tableName, newTable.data())) {
2914         return nullptr;
2915     }
2916     return d->setupTableSchema(newTable.take());
2917 }
2918 
tableSchema(int tableId)2919 KDbTableSchema* KDbConnection::tableSchema(int tableId)
2920 {
2921     KDbTableSchema *t = d->table(tableId);
2922     if (t)
2923         return t;
2924     //not found: retrieve schema
2925     QScopedPointer<KDbTableSchema> newTable(new KDbTableSchema);
2926     clearResult();
2927     if (true != loadObjectData(KDb::TableObjectType, tableId, newTable.data())) {
2928         return nullptr;
2929     }
2930     return d->setupTableSchema(newTable.take());
2931 }
2932 
loadDataBlock(int objectID,QString * dataString,const QString & dataID)2933 tristate KDbConnection::loadDataBlock(int objectID, QString* dataString, const QString& dataID)
2934 {
2935     if (objectID <= 0)
2936         return false;
2937     return querySingleString(
2938                KDbEscapedString("SELECT o_data FROM kexi__objectdata WHERE o_id=%1 AND ")
2939                                 .arg(d->driver->valueToSql(KDbField::Integer, objectID))
2940                                 + KDbEscapedString(KDb::sqlWhere(d->driver, KDbField::Text,
2941                                                 QLatin1String("o_sub_id"),
2942                                                 dataID.isEmpty() ? QVariant() : QVariant(dataID))),
2943                dataString);
2944 }
2945 
storeDataBlock(int objectID,const QString & dataString,const QString & dataID)2946 bool KDbConnection::storeDataBlock(int objectID, const QString &dataString, const QString& dataID)
2947 {
2948     if (objectID <= 0)
2949         return false;
2950     KDbEscapedString sql(
2951         KDbEscapedString("SELECT kexi__objectdata.o_id FROM kexi__objectdata WHERE o_id=%1")
2952                         .arg(d->driver->valueToSql(KDbField::Integer, objectID)));
2953     KDbEscapedString sql_sub(KDb::sqlWhere(d->driver, KDbField::Text, QLatin1String("o_sub_id"),
2954                                               dataID.isEmpty() ? QVariant() : QVariant(dataID)));
2955 
2956     const tristate result = resultExists(sql + " AND " + sql_sub);
2957     if (~result) {
2958         return false;
2959     }
2960     if (result == true) {
2961         return executeSql(KDbEscapedString("UPDATE kexi__objectdata SET o_data=%1 WHERE o_id=%2 AND ")
2962                           .arg(d->driver->valueToSql(KDbField::LongText, dataString))
2963                           .arg(d->driver->valueToSql(KDbField::Integer, objectID))
2964                           + sql_sub);
2965     }
2966     return executeSql(
2967                KDbEscapedString("INSERT INTO kexi__objectdata (o_id, o_data, o_sub_id) VALUES (")
2968                + KDbEscapedString::number(objectID) + ',' + d->driver->valueToSql(KDbField::LongText, dataString)
2969                + ',' + d->driver->valueToSql(KDbField::Text, dataID) + ')');
2970 }
2971 
copyDataBlock(int sourceObjectID,int destObjectID,const QString & dataID)2972 bool KDbConnection::copyDataBlock(int sourceObjectID, int destObjectID, const QString &dataID)
2973 {
2974     if (sourceObjectID <= 0 || destObjectID <= 0)
2975         return false;
2976     if (sourceObjectID == destObjectID)
2977         return true;
2978     if (!removeDataBlock(destObjectID, dataID)) // remove before copying
2979         return false;
2980     KDbEscapedString sql = KDbEscapedString(
2981          "INSERT INTO kexi__objectdata SELECT %1, t.o_data, t.o_sub_id "
2982          "FROM kexi__objectdata AS t WHERE o_id=%2")
2983          .arg(d->driver->valueToSql(KDbField::Integer, destObjectID))
2984          .arg(d->driver->valueToSql(KDbField::Integer, sourceObjectID));
2985     if (!dataID.isEmpty()) {
2986         sql += KDbEscapedString(" AND ") + KDb::sqlWhere(d->driver, KDbField::Text,
2987                                                             QLatin1String("o_sub_id"), dataID);
2988     }
2989     return executeSql(sql);
2990 }
2991 
removeDataBlock(int objectID,const QString & dataID)2992 bool KDbConnection::removeDataBlock(int objectID, const QString& dataID)
2993 {
2994     if (objectID <= 0)
2995         return false;
2996     if (dataID.isEmpty())
2997         return KDb::deleteRecords(this, QLatin1String("kexi__objectdata"),
2998                                        QLatin1String("o_id"), QString::number(objectID));
2999     else
3000         return KDb::deleteRecords(this, QLatin1String("kexi__objectdata"),
3001                                        QLatin1String("o_id"), KDbField::Integer, objectID,
3002                                        QLatin1String("o_sub_id"), KDbField::Text, dataID);
3003 }
3004 
querySchema(const QString & aQueryName)3005 KDbQuerySchema* KDbConnection::querySchema(const QString& aQueryName)
3006 {
3007     QString queryName = aQueryName.toLower();
3008     KDbQuerySchema *q = d->query(queryName);
3009     if (q || queryName.isEmpty()) {
3010         return q;
3011     }
3012     //not found: retrieve schema
3013     QScopedPointer<KDbQuerySchema> newQuery(new KDbQuerySchema);
3014     clearResult();
3015     if (true != loadObjectData(KDb::QueryObjectType, aQueryName, newQuery.data())) {
3016         return nullptr;
3017     }
3018     return d->setupQuerySchema(newQuery.take());
3019 }
3020 
querySchema(int queryId)3021 KDbQuerySchema* KDbConnection::querySchema(int queryId)
3022 {
3023     KDbQuerySchema *q = d->query(queryId);
3024     if (q)
3025         return q;
3026     //not found: retrieve schema
3027     QScopedPointer<KDbQuerySchema> newQuery(new KDbQuerySchema);
3028     clearResult();
3029     if (true != loadObjectData(KDb::QueryObjectType, queryId, newQuery.data())) {
3030         return nullptr;
3031     }
3032     return d->setupQuerySchema(newQuery.take());
3033 }
3034 
setQuerySchemaObsolete(const QString & queryName)3035 bool KDbConnection::setQuerySchemaObsolete(const QString& queryName)
3036 {
3037     KDbQuerySchema* oldQuery = querySchema(queryName);
3038     if (!oldQuery)
3039         return false;
3040     d->setQueryObsolete(oldQuery);
3041     return true;
3042 }
3043 
escapeIdentifier(const QString & id) const3044 QString KDbConnection::escapeIdentifier(const QString& id) const
3045 {
3046     return d->driver->escapeIdentifier(id);
3047 }
3048 
isInternalTableSchema(const QString & tableName)3049 bool KDbConnection::isInternalTableSchema(const QString& tableName)
3050 {
3051     KDbTableSchema* schema = d->table(tableName);
3052     return (schema && schema->isInternal())
3053            // these are here for compatiblility because we're no longer instantiate
3054            // them but can exist in projects created with previous Kexi versions:
3055            || tableName == QLatin1String("kexi__final") || tableName == QLatin1String("kexi__useractions");
3056 }
3057 
removeMe(KDbTableSchema * table)3058 void KDbConnection::removeMe(KDbTableSchema *table)
3059 {
3060     if (table && d) {
3061         d->takeTable(table);
3062     }
3063 }
3064 
anyAvailableDatabaseName()3065 QString KDbConnection::anyAvailableDatabaseName()
3066 {
3067     if (!d->availableDatabaseName.isEmpty()) {
3068         return d->availableDatabaseName;
3069     }
3070     return d->driver->behavior()->ALWAYS_AVAILABLE_DATABASE_NAME;
3071 }
3072 
setAvailableDatabaseName(const QString & dbName)3073 void KDbConnection::setAvailableDatabaseName(const QString& dbName)
3074 {
3075     d->availableDatabaseName = dbName;
3076 }
3077 
3078 //! @internal used in updateRecord(), insertRecord(),
updateRecordDataWithNewValues(KDbConnection * conn,KDbQuerySchema * query,KDbRecordData * data,const KDbRecordEditBuffer::DbHash & b,QHash<KDbQueryColumnInfo *,int> * columnsOrderExpanded)3079 inline static void updateRecordDataWithNewValues(
3080         KDbConnection *conn, KDbQuerySchema* query, KDbRecordData* data,
3081         const KDbRecordEditBuffer::DbHash& b,
3082         QHash<KDbQueryColumnInfo*, int>* columnsOrderExpanded)
3083 {
3084     *columnsOrderExpanded
3085         = query->columnsOrder(conn, KDbQuerySchema::ColumnsOrderMode::ExpandedList);
3086     QHash<KDbQueryColumnInfo*, int>::ConstIterator columnsOrderExpandedIt;
3087     for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) {
3088         columnsOrderExpandedIt = columnsOrderExpanded->constFind(it.key());
3089         if (columnsOrderExpandedIt == columnsOrderExpanded->constEnd()) {
3090             kdbWarning() << "(KDbConnection) \"now also assign new value in memory\" step"
3091                        "- could not find item" << it.key()->aliasOrName();
3092             continue;
3093         }
3094         (*data)[ columnsOrderExpandedIt.value() ] = it.value();
3095     }
3096 }
3097 
updateRecord(KDbQuerySchema * query,KDbRecordData * data,KDbRecordEditBuffer * buf,bool useRecordId)3098 bool KDbConnection::updateRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool useRecordId)
3099 {
3100 // Each SQL identifier needs to be escaped in the generated query.
3101 // kdbDebug() << *query;
3102 
3103     clearResult();
3104     //--get PKEY
3105     if (buf->dbBuffer().isEmpty()) {
3106         kdbDebug() << " -- NO CHANGES DATA!";
3107         return true;
3108     }
3109     KDbTableSchema *mt = query->masterTable();
3110     if (!mt) {
3111         kdbWarning() << " -- NO MASTER TABLE!";
3112         m_result = KDbResult(ERR_UPDATE_NO_MASTER_TABLE,
3113                              tr("Could not update record because there is no master table defined."));
3114         return false;
3115     }
3116     KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr;
3117     if (!useRecordId && !pkey) {
3118         kdbWarning() << " -- NO MASTER TABLE's PKEY!";
3119         m_result = KDbResult(ERR_UPDATE_NO_MASTER_TABLES_PKEY,
3120             tr("Could not update record because master table has no primary key defined."));
3121 //! @todo perhaps we can try to update without using PKEY?
3122         return false;
3123     }
3124     //update the record:
3125     KDbEscapedString sql;
3126     sql.reserve(4096);
3127     sql = KDbEscapedString("UPDATE ") + escapeIdentifier(mt->name()) + " SET ";
3128     KDbEscapedString sqlset, sqlwhere;
3129     sqlset.reserve(1024);
3130     sqlwhere.reserve(1024);
3131     KDbRecordEditBuffer::DbHash b = buf->dbBuffer();
3132 
3133     //gather the fields which are updated ( have values in KDbRecordEditBuffer)
3134     KDbFieldList affectedFields;
3135     for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) {
3136         if (it.key()->field()->table() != mt)
3137             continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
3138         if (!sqlset.isEmpty())
3139             sqlset += ',';
3140         KDbField* currentField = it.key()->field();
3141         const bool affectedFieldsAddOk = affectedFields.addField(currentField);
3142         Q_ASSERT(affectedFieldsAddOk);
3143         sqlset += KDbEscapedString(escapeIdentifier(currentField->name())) + '=' +
3144                   d->driver->valueToSql(currentField, it.value());
3145     }
3146     if (pkey) {
3147         const QVector<int> pkeyFieldsOrder(query->pkeyFieldsOrder(this));
3148         //kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount();
3149         if (pkey->fieldCount() != query->pkeyFieldCount(this)) { //sanity check
3150             kdbWarning() << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!";
3151             m_result = KDbResult(ERR_UPDATE_NO_ENTIRE_MASTER_TABLES_PKEY,
3152                                  tr("Could not update record because it does not contain entire primary key of master table."));
3153             return false;
3154         }
3155         if (!pkey->fields()->isEmpty()) {
3156             int i = 0;
3157             foreach(KDbField *f, *pkey->fields()) {
3158                 if (!sqlwhere.isEmpty())
3159                     sqlwhere += " AND ";
3160                 QVariant val(data->at(pkeyFieldsOrder.at(i)));
3161                 if (val.isNull() || !val.isValid()) {
3162                     m_result = KDbResult(ERR_UPDATE_NULL_PKEY_FIELD,
3163                                          tr("Primary key's field \"%1\" cannot be empty.").arg(f->name()));
3164                     //js todo: pass the field's name somewhere!
3165                     return false;
3166                 }
3167                 sqlwhere += KDbEscapedString(escapeIdentifier(f->name())) + '=' +
3168                             d->driver->valueToSql(f, val);
3169                 i++;
3170             }
3171         }
3172     } else { //use RecordId
3173         sqlwhere = KDbEscapedString(escapeIdentifier(d->driver->behavior()->ROW_ID_FIELD_NAME)) + '='
3174                    + d->driver->valueToSql(KDbField::BigInteger, (*data)[data->size() - 1]);
3175     }
3176     sql += (sqlset + " WHERE " + sqlwhere);
3177     //kdbDebug() << " -- SQL == " << ((sql.length() > 400) ? (sql.left(400) + "[.....]") : sql);
3178 
3179     // preprocessing before update
3180     if (!drv_beforeUpdate(mt->name(), &affectedFields))
3181         return false;
3182 
3183     bool res = executeSql(sql);
3184 
3185     // postprocessing after update
3186     if (!drv_afterUpdate(mt->name(), &affectedFields))
3187         return false;
3188 
3189     if (!res) {
3190         m_result = KDbResult(ERR_UPDATE_SERVER_ERROR,
3191                              tr("Record updating on the server failed."));
3192         return false;
3193     }
3194     //success: now also assign new values in memory:
3195     QHash<KDbQueryColumnInfo*, int> columnsOrderExpanded;
3196     updateRecordDataWithNewValues(this, query, data, b, &columnsOrderExpanded);
3197     return true;
3198 }
3199 
insertRecord(KDbQuerySchema * query,KDbRecordData * data,KDbRecordEditBuffer * buf,bool getRecordId)3200 bool KDbConnection::insertRecord(KDbQuerySchema* query, KDbRecordData* data, KDbRecordEditBuffer* buf, bool getRecordId)
3201 {
3202 // Each SQL identifier needs to be escaped in the generated query.
3203     clearResult();
3204     //--get PKEY
3205     /*disabled: there may be empty records (with autoinc)
3206     if (buf.dbBuffer().isEmpty()) {
3207       kdbDebug() << " -- NO CHANGES DATA!";
3208       return true; }*/
3209     KDbTableSchema *mt = query->masterTable();
3210     if (!mt) {
3211         kdbWarning() << " -- NO MASTER TABLE!";
3212         m_result = KDbResult(ERR_INSERT_NO_MASTER_TABLE,
3213                              tr("Could not insert record because there is no master table specified."));
3214         return false;
3215     }
3216     KDbIndexSchema *pkey
3217         = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr;
3218     if (!getRecordId && !pkey) {
3219         kdbWarning() << " -- WARNING: NO MASTER TABLE's PKEY";
3220     }
3221 
3222     KDbEscapedString sqlcols, sqlvals;
3223     sqlcols.reserve(1024);
3224     sqlvals.reserve(1024);
3225 
3226     //insert the record:
3227     KDbEscapedString sql;
3228     sql.reserve(4096);
3229     sql = KDbEscapedString("INSERT INTO ") + escapeIdentifier(mt->name()) + " (";
3230     KDbRecordEditBuffer::DbHash b = buf->dbBuffer();
3231 
3232     // add default values, if available (for any column without value explicitly set)
3233     const KDbQueryColumnInfo::Vector fieldsExpanded(
3234         query->fieldsExpanded(this, KDbQuerySchema::FieldsExpandedMode::Unique));
3235     int fieldsExpandedCount = fieldsExpanded.count();
3236     for (int i = 0; i < fieldsExpandedCount; i++) {
3237         KDbQueryColumnInfo *ci = fieldsExpanded.at(i);
3238         if (ci->field() && KDb::isDefaultValueAllowed(*ci->field())
3239                 && !ci->field()->defaultValue().isNull()
3240                 && !b.contains(ci))
3241         {
3242             //kdbDebug() << "adding default value" << ci->field->defaultValue().toString() << "for column" << ci->field->name();
3243             b.insert(ci, ci->field()->defaultValue());
3244         }
3245     }
3246 
3247     //collect fields which have values in KDbRecordEditBuffer
3248     KDbFieldList affectedFields;
3249 
3250     if (b.isEmpty()) {
3251         // empty record inserting requested:
3252         if (!getRecordId && !pkey) {
3253             kdbWarning() << "MASTER TABLE's PKEY REQUIRED FOR INSERTING EMPTY RECORDS: INSERT CANCELLED";
3254             m_result = KDbResult(ERR_INSERT_NO_MASTER_TABLES_PKEY,
3255                                  tr("Could not insert record because master table has no primary key specified."));
3256             return false;
3257         }
3258         if (pkey) {
3259             const QVector<int> pkeyFieldsOrder(query->pkeyFieldsOrder(this));
3260             //   kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount();
3261             if (pkey->fieldCount() != query->pkeyFieldCount(this)) { // sanity check
3262                 kdbWarning() << "NO ENTIRE MASTER TABLE's PKEY SPECIFIED!";
3263                 m_result = KDbResult(ERR_INSERT_NO_ENTIRE_MASTER_TABLES_PKEY,
3264                                      tr("Could not insert record because it does not contain "
3265                                         "entire master table's primary key."));
3266                 return false;
3267             }
3268         }
3269         //at least one value is needed for VALUES section: find it and set to NULL:
3270         KDbField *anyField = mt->anyNonPKField();
3271         if (!anyField) {
3272             if (!pkey) {
3273                 kdbWarning() << "WARNING: NO FIELD AVAILABLE TO SET IT TO NULL";
3274                 return false;
3275             } else {
3276                 //try to set NULL in pkey field (could not work for every SQL engine!)
3277                 anyField = pkey->fields()->first();
3278             }
3279         }
3280         sqlcols += escapeIdentifier(anyField->name());
3281         sqlvals += d->driver->valueToSql(anyField, QVariant()/*NULL*/);
3282         const bool affectedFieldsAddOk = affectedFields.addField(anyField);
3283         Q_ASSERT(affectedFieldsAddOk);
3284     } else {
3285         // non-empty record inserting requested:
3286         for (KDbRecordEditBuffer::DbHash::ConstIterator it = b.constBegin();it != b.constEnd();++it) {
3287             if (it.key()->field()->table() != mt)
3288                 continue; // skip values for fields outside of the master table (e.g. a "visible value" of the lookup field)
3289             if (!sqlcols.isEmpty()) {
3290                 sqlcols += ',';
3291                 sqlvals += ',';
3292             }
3293             KDbField* currentField = it.key()->field();
3294             const bool affectedFieldsAddOk = affectedFields.addField(currentField);
3295             Q_ASSERT(affectedFieldsAddOk);
3296             sqlcols += escapeIdentifier(currentField->name());
3297             sqlvals += d->driver->valueToSql(currentField, it.value());
3298         }
3299     }
3300     sql += (sqlcols + ") VALUES (" + sqlvals + ')');
3301 // kdbDebug() << " -- SQL == " << sql;
3302 
3303     // low-level insert
3304     QSharedPointer<KDbSqlResult> result = insertRecordInternal(mt->name(), &affectedFields, sql);
3305     if (!result) {
3306         m_result = KDbResult(ERR_INSERT_SERVER_ERROR,
3307                              tr("Record inserting on the server failed."));
3308         return false;
3309     }
3310     //success: now also assign a new value in memory:
3311     QHash<KDbQueryColumnInfo*, int> columnsOrderExpanded;
3312     updateRecordDataWithNewValues(this, query, data, b, &columnsOrderExpanded);
3313 
3314     //fetch autoincremented values
3315     KDbQueryColumnInfo::List *aif_list = query->autoIncrementFields(this);
3316     quint64 recordId = 0;
3317     if (pkey && !aif_list->isEmpty()) {
3318         //! @todo now only if PKEY is present, this should also work when there's no PKEY
3319         KDbQueryColumnInfo *id_columnInfo = aif_list->first();
3320         //! @todo safe to cast it?
3321         quint64 last_id
3322             = KDb::lastInsertedAutoIncValue(result, id_columnInfo->field()->name(),
3323                                             id_columnInfo->field()->table()->name(), &recordId);
3324         if (last_id == std::numeric_limits<quint64>::max()) {
3325             //! @todo show error
3326 //! @todo remove just inserted record. How? Using ROLLBACK?
3327             return false;
3328         }
3329         KDbRecordData aif_data;
3330         KDbEscapedString getAutoIncForInsertedValue("SELECT "
3331                                              + query->autoIncrementSqlFieldsList(this)
3332                                              + " FROM "
3333                                              + escapeIdentifier(id_columnInfo->field()->table()->name())
3334                                              + " WHERE "
3335                                              + escapeIdentifier(id_columnInfo->field()->name()) + '='
3336                                              + QByteArray::number(last_id));
3337         if (true != querySingleRecord(getAutoIncForInsertedValue, &aif_data)) {
3338             //! @todo show error
3339             return false;
3340         }
3341         int i = 0;
3342         foreach(KDbQueryColumnInfo *ci, *aif_list) {
3343 //   kdbDebug() << "AUTOINCREMENTED FIELD" << fi->field->name() << "==" << aif_data[i].toInt();
3344             ((*data)[ columnsOrderExpanded.value(ci)]
3345                 = aif_data.value(i)).convert(ci->field()->variantType()); //cast to get proper type
3346             i++;
3347         }
3348     } else {
3349         recordId = result->lastInsertRecordId();
3350 //  kdbDebug() << "new recordId ==" << recordId;
3351         if (d->driver->behavior()->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE) {
3352             kdbWarning() << "d->driver->behavior()->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE";
3353             return false;
3354         }
3355     }
3356     if (getRecordId && /*sanity check*/data->size() > fieldsExpanded.size()) {
3357 //  kdbDebug() << "new ROWID ==" << ROWID;
3358         (*data)[data->size() - 1] = recordId;
3359     }
3360     return true;
3361 }
3362 
deleteRecord(KDbQuerySchema * query,KDbRecordData * data,bool useRecordId)3363 bool KDbConnection::deleteRecord(KDbQuerySchema* query, KDbRecordData* data, bool useRecordId)
3364 {
3365 // Each SQL identifier needs to be escaped in the generated query.
3366     clearResult();
3367     KDbTableSchema *mt = query->masterTable();
3368     if (!mt) {
3369         kdbWarning() << " -- NO MASTER TABLE!";
3370         m_result = KDbResult(ERR_DELETE_NO_MASTER_TABLE,
3371                              tr("Could not delete record because there is no master table specified."));
3372         return false;
3373     }
3374     KDbIndexSchema *pkey = (mt->primaryKey() && !mt->primaryKey()->fields()->isEmpty()) ? mt->primaryKey() : nullptr;
3375 
3376 //! @todo allow to delete from a table without pkey
3377     if (!useRecordId && !pkey) {
3378         kdbWarning() << " -- WARNING: NO MASTER TABLE's PKEY";
3379         m_result = KDbResult(ERR_DELETE_NO_MASTER_TABLES_PKEY,
3380                              tr("Could not delete record because there is no primary key for master table specified."));
3381         return false;
3382     }
3383 
3384     //update the record:
3385     KDbEscapedString sql;
3386     sql.reserve(4096);
3387     sql = KDbEscapedString("DELETE FROM ") + escapeIdentifier(mt->name()) + " WHERE ";
3388     KDbEscapedString sqlwhere;
3389     sqlwhere.reserve(1024);
3390 
3391     if (pkey) {
3392         const QVector<int> pkeyFieldsOrder(query->pkeyFieldsOrder(this));
3393         //kdbDebug() << pkey->fieldCount() << " ? " << query->pkeyFieldCount();
3394         if (pkey->fieldCount() != query->pkeyFieldCount(this)) { //sanity check
3395             kdbWarning() << " -- NO ENTIRE MASTER TABLE's PKEY SPECIFIED!";
3396             m_result = KDbResult(ERR_DELETE_NO_ENTIRE_MASTER_TABLES_PKEY,
3397                                  tr("Could not delete record because it does not contain entire master table's primary key."));
3398             return false;
3399         }
3400         int i = 0;
3401         foreach(KDbField *f, *pkey->fields()) {
3402             if (!sqlwhere.isEmpty())
3403                 sqlwhere += " AND ";
3404             QVariant val(data->at(pkeyFieldsOrder.at(i)));
3405             if (val.isNull() || !val.isValid()) {
3406                 m_result = KDbResult(ERR_DELETE_NULL_PKEY_FIELD,
3407                                      tr("Primary key's field \"%1\" cannot be empty.").arg(f->name()));
3408 //js todo: pass the field's name somewhere!
3409                 return false;
3410             }
3411             sqlwhere += KDbEscapedString(escapeIdentifier(f->name())) + '=' +
3412                          d->driver->valueToSql(f, val);
3413             i++;
3414         }
3415     } else {//use RecordId
3416         sqlwhere = KDbEscapedString(escapeIdentifier(d->driver->behavior()->ROW_ID_FIELD_NAME)) + '='
3417                     + d->driver->valueToSql(KDbField::BigInteger, (*data)[data->size() - 1]);
3418     }
3419     sql += sqlwhere;
3420     //kdbDebug() << " -- SQL == " << sql;
3421 
3422     if (!executeSql(sql)) {
3423         m_result = KDbResult(ERR_DELETE_SERVER_ERROR,
3424                              tr("Record deletion on the server failed."));
3425         return false;
3426     }
3427     return true;
3428 }
3429 
deleteAllRecords(KDbQuerySchema * query)3430 bool KDbConnection::deleteAllRecords(KDbQuerySchema* query)
3431 {
3432     clearResult();
3433     KDbTableSchema *mt = query->masterTable();
3434     if (!mt) {
3435         kdbWarning() << " -- NO MASTER TABLE!";
3436         return false;
3437     }
3438     KDbIndexSchema *pkey = mt->primaryKey();
3439     if (!pkey || pkey->fields()->isEmpty()) {
3440         kdbWarning() << "-- WARNING: NO MASTER TABLE's PKEY";
3441     }
3442     KDbEscapedString sql = KDbEscapedString("DELETE FROM ") + escapeIdentifier(mt->name());
3443     //kdbDebug() << "-- SQL == " << sql;
3444 
3445     if (!executeSql(sql)) {
3446         m_result = KDbResult(ERR_DELETE_SERVER_ERROR,
3447                              tr("Record deletion on the server failed."));
3448         return false;
3449     }
3450     return true;
3451 }
3452 
recordCount(const KDbEscapedString & sql)3453 int KDbConnection::recordCount(const KDbEscapedString& sql)
3454 {
3455     int count = -1; //will be changed only on success of querySingleNumber()
3456     const tristate result = querySingleNumber(
3457         KDbEscapedString("SELECT COUNT() FROM (") + sql + ") AS kdb__subquery", &count);
3458     if (~result) {
3459         count = 0;
3460     }
3461     return count;
3462 }
3463 
recordCount(const KDbTableSchema & tableSchema)3464 int KDbConnection::recordCount(const KDbTableSchema& tableSchema)
3465 {
3466     //! @todo does not work with non-SQL data sources
3467     int count = -1; // will be changed only on success of querySingleNumber()
3468     const tristate result = querySingleNumber(
3469         KDbEscapedString("SELECT COUNT(*) FROM ") + escapeIdentifier(tableSchema.name()), &count);
3470     if (~result) {
3471         count = 0;
3472     }
3473     return count;
3474 }
3475 
recordCount(KDbQuerySchema * querySchema,const QList<QVariant> & params)3476 int KDbConnection::recordCount(KDbQuerySchema* querySchema, const QList<QVariant>& params)
3477 {
3478 //! @todo does not work with non-SQL data sources
3479     int count = -1; //will be changed only on success of querySingleNumber()
3480     KDbNativeStatementBuilder builder(this, KDb::DriverEscaping);
3481     KDbEscapedString subSql;
3482     if (!builder.generateSelectStatement(&subSql, querySchema, params)) {
3483         return -1;
3484     }
3485     const tristate result = querySingleNumber(
3486         KDbEscapedString("SELECT COUNT(*) FROM (") + subSql + ") AS kdb__subquery", &count);
3487     if (~result) {
3488         count = 0;
3489     }
3490     return count;
3491 }
3492 
recordCount(KDbTableOrQuerySchema * tableOrQuery,const QList<QVariant> & params)3493 int KDbConnection::recordCount(KDbTableOrQuerySchema* tableOrQuery, const QList<QVariant>& params)
3494 {
3495     if (tableOrQuery) {
3496         if (tableOrQuery->table())
3497             return recordCount(*tableOrQuery->table());
3498         if (tableOrQuery->query())
3499             return recordCount(tableOrQuery->query(), params);
3500     }
3501     return -1;
3502 }
3503 
options()3504 KDbConnectionOptions* KDbConnection::options()
3505 {
3506     return &d->options;
3507 }
3508 
addCursor(KDbCursor * cursor)3509 void KDbConnection::addCursor(KDbCursor* cursor)
3510 {
3511     d->cursors.insert(cursor);
3512 }
3513 
takeCursor(KDbCursor * cursor)3514 void KDbConnection::takeCursor(KDbCursor* cursor)
3515 {
3516     if (d && !d->cursors.isEmpty()) { // checking because this may be called from ~KDbConnection()
3517         d->cursors.remove(cursor);
3518     }
3519 }
3520 
prepareStatement(KDbPreparedStatement::Type type,KDbFieldList * fields,const QStringList & whereFieldNames)3521 KDbPreparedStatement KDbConnection::prepareStatement(KDbPreparedStatement::Type type,
3522     KDbFieldList* fields, const QStringList& whereFieldNames)
3523 {
3524 //! @todo move to ConnectionInterface just like we moved execute() and prepare() to KDbPreparedStatementInterface...
3525     KDbPreparedStatementInterface *iface = prepareStatementInternal();
3526     if (!iface)
3527         return KDbPreparedStatement();
3528     return KDbPreparedStatement(iface, type, fields, whereFieldNames);
3529 }
3530 
recentSqlString() const3531 KDbEscapedString KDbConnection::recentSqlString() const {
3532     return result().errorSql().isEmpty() ? m_result.sql() : result().errorSql();
3533 }
3534 
escapeString(const QString & str) const3535 KDbEscapedString KDbConnection::escapeString(const QString& str) const
3536 {
3537     return d->driver->escapeString(str);
3538 }
3539 
3540 //! @todo extraMessages
3541 #if 0
3542 static const char *extraMessages[] = {
3543     QT_TRANSLATE_NOOP("KDbConnection", "Unknown error.")
3544 };
3545 #endif
3546