File indexing completed on 2024-04-21 15:30:19

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KDbQuerySchema.h"
0021 #include "KDbQuerySchema_p.h"
0022 #include "KDbQueryAsterisk.h"
0023 #include "KDbConnection.h"
0024 #include "KDbConnection_p.h"
0025 #include "kdb_debug.h"
0026 #include "KDbLookupFieldSchema.h"
0027 #include "KDbOrderByColumn.h"
0028 #include "KDbParser_p.h"
0029 #include "KDbQuerySchemaParameter.h"
0030 #include "KDbRelationship.h"
0031 
0032 QString escapeIdentifier(const QString& name, KDbConnection *conn,
0033                                 KDb::IdentifierEscapingType escapingType)
0034 {
0035     switch (escapingType) {
0036     case KDb::DriverEscaping:
0037         if (conn)
0038             return conn->escapeIdentifier(name);
0039         break;
0040     case KDb::KDbEscaping:
0041         return KDb::escapeIdentifier(name);
0042     }
0043     return QLatin1Char('"') + name + QLatin1Char('"');
0044 }
0045 
0046 KDbQuerySchema::KDbQuerySchema()
0047         : KDbFieldList(false)//fields are not owned by KDbQuerySchema object
0048         , KDbObject(KDb::QueryObjectType)
0049         , d(new KDbQuerySchemaPrivate(this))
0050 {
0051 }
0052 
0053 KDbQuerySchema::KDbQuerySchema(KDbTableSchema *tableSchema)
0054         : KDbFieldList(false)//fields are not owned by KDbQuerySchema object
0055         , KDbObject(KDb::QueryObjectType)
0056         , d(new KDbQuerySchemaPrivate(this))
0057 {
0058     if (tableSchema) {
0059         d->masterTable = tableSchema;
0060         /*if (!d->masterTable) {
0061           kdbWarning() << "!d->masterTable";
0062           m_name.clear();
0063           return;
0064         }*/
0065         addTable(d->masterTable);
0066         //defaults:
0067         //inherit name from a table
0068         setName(d->masterTable->name());
0069         //inherit caption from a table
0070         setCaption(d->masterTable->caption());
0071 
0072         // add explicit field list to avoid problems (e.g. with fields added outside of the app):
0073         foreach(KDbField* f, *d->masterTable->fields()) {
0074             addField(f);
0075         }
0076     }
0077 }
0078 
0079 KDbQuerySchema::KDbQuerySchema(const KDbQuerySchema& querySchema, KDbConnection *conn)
0080         : KDbFieldList(querySchema, false /* !deepCopyFields */)
0081         , KDbObject(querySchema)
0082         , d(new KDbQuerySchemaPrivate(this, querySchema.d))
0083 {
0084     //only deep copy query asterisks
0085     for (KDbField* f :  qAsConst(*querySchema.fields())) {
0086         KDbField *copiedField;
0087         if (dynamic_cast<KDbQueryAsterisk*>(f)) {
0088             copiedField = f->copy();
0089             if (static_cast<const KDbFieldList *>(f->parent()) == &querySchema) {
0090                 copiedField->setParent(this);
0091             }
0092         }
0093         else {
0094             copiedField = f;
0095         }
0096         addField(copiedField);
0097     }
0098     // this deep copy must be after the 'd' initialization because fieldsExpanded() is used there
0099     d->orderByColumnList = new KDbOrderByColumnList(*querySchema.d->orderByColumnList, conn,
0100                                                     const_cast<KDbQuerySchema*>(&querySchema), this);
0101 }
0102 
0103 KDbQuerySchema::~KDbQuerySchema()
0104 {
0105     delete d;
0106 }
0107 
0108 void KDbQuerySchema::clear()
0109 {
0110     KDbFieldList::clear();
0111     KDbObject::clear();
0112     d->clear();
0113 }
0114 
0115 /*virtual*/
0116 bool KDbQuerySchema::insertField(int position, KDbField *field)
0117 {
0118     return insertFieldInternal(position, field, -1/*don't bind*/, true);
0119 }
0120 
0121 bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field)
0122 {
0123     return insertFieldInternal(position, field, -1/*don't bind*/, false);
0124 }
0125 
0126 bool KDbQuerySchema::insertField(int position, KDbField *field, int bindToTable)
0127 {
0128     return insertFieldInternal(position, field, bindToTable, true);
0129 }
0130 
0131 bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field, int bindToTable)
0132 {
0133     return insertFieldInternal(position, field, bindToTable, false);
0134 }
0135 
0136 bool KDbQuerySchema::insertFieldInternal(int position, KDbField *field,
0137                                          int bindToTable, bool visible)
0138 {
0139     if (!field) {
0140         kdbWarning() << "!field";
0141         return false;
0142     }
0143 
0144     if (position > fieldCount()) {
0145         kdbWarning() << "position" << position << "out of range";
0146         return false;
0147     }
0148     if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) {
0149         kdbWarning() << "field" << field->name() << "must contain table information!";
0150         return false;
0151     }
0152     if (fieldCount() >= d->visibility.size()) {
0153         d->visibility.resize(d->visibility.size()*2);
0154         d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2);
0155     }
0156     d->clearCachedData();
0157     if (!KDbFieldList::insertField(position, field)) {
0158         return false;
0159     }
0160     if (field->isQueryAsterisk()) {
0161         d->asterisks.append(field);
0162         //if this is single-table asterisk,
0163         //add a table to list if doesn't exist there:
0164         if (field->table() && !d->tables.contains(field->table()))
0165             d->tables.append(field->table());
0166     } else if (field->table()) {
0167         //add a table to list if doesn't exist there:
0168         if (!d->tables.contains(field->table()))
0169             d->tables.append(field->table());
0170     }
0171     //update visibility
0172     //--move bits to make a place for a new one
0173     for (int i = fieldCount() - 1; i > position; i--)
0174         d->visibility.setBit(i, d->visibility.testBit(i - 1));
0175     d->visibility.setBit(position, visible);
0176 
0177     //bind to table
0178     if (bindToTable < -1 || bindToTable > d->tables.count()) {
0179         kdbWarning() << "bindToTable" << bindToTable << "out of range";
0180         bindToTable = -1;
0181     }
0182     //--move items to make a place for a new one
0183     for (int i = fieldCount() - 1; i > position; i--)
0184         d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1];
0185     d->tablesBoundToColumns[position] = bindToTable;
0186 
0187 #ifdef KDB_QUERYSCHEMA_DEBUG
0188     querySchemaDebug() << "bound to table" << bindToTable;
0189     if (bindToTable == -1)
0190         querySchemaDebug() << " <NOT SPECIFIED>";
0191     else
0192         querySchemaDebug() << " name=" << d->tables.at(bindToTable)->name()
0193                            << " alias=" << tableAlias(bindToTable);
0194     QString s;
0195     for (int i = 0; i < fieldCount();i++)
0196         s += (QString::number(d->tablesBoundToColumns[i]) + QLatin1Char(' '));
0197     querySchemaDebug() << "tablesBoundToColumns == [" << s << "]";
0198 #endif
0199 
0200     if (field->isExpression())
0201         d->regenerateExprAliases = true;
0202 
0203     return true;
0204 }
0205 
0206 int KDbQuerySchema::tableBoundToColumn(int columnPosition) const
0207 {
0208     int res = d->tablesBoundToColumns.value(columnPosition, -99);
0209     if (res == -99) {
0210         kdbWarning() << "columnPosition" << columnPosition << "out of range";
0211         return -1;
0212     }
0213     return res;
0214 }
0215 
0216 bool KDbQuerySchema::addField(KDbField* field)
0217 {
0218     return insertField(fieldCount(), field);
0219 }
0220 
0221 bool KDbQuerySchema::addField(KDbField* field, int bindToTable)
0222 {
0223     return insertField(fieldCount(), field, bindToTable);
0224 }
0225 
0226 bool KDbQuerySchema::addInvisibleField(KDbField* field)
0227 {
0228     return insertInvisibleField(fieldCount(), field);
0229 }
0230 
0231 bool KDbQuerySchema::addInvisibleField(KDbField* field, int bindToTable)
0232 {
0233     return insertInvisibleField(fieldCount(), field, bindToTable);
0234 }
0235 
0236 bool KDbQuerySchema::removeField(KDbField *field)
0237 {
0238     int indexOfAsterisk = -1;
0239     if (field->isQueryAsterisk()) {
0240         indexOfAsterisk = d->asterisks.indexOf(field);
0241     }
0242     if (!KDbFieldList::removeField(field)) {
0243         return false;
0244     }
0245     d->clearCachedData();
0246     if (indexOfAsterisk >= 0) {
0247         //querySchemaDebug() << "d->asterisks.removeAt:" << field;
0248         //field->debug();
0249         d->asterisks.removeAt(indexOfAsterisk); //this will destroy this asterisk
0250     }
0251 //! @todo should we also remove table for this field or asterisk?
0252     return true;
0253 }
0254 
0255 bool KDbQuerySchema::addExpressionInternal(const KDbExpression& expr, bool visible)
0256 {
0257     KDbField *field = new KDbField(this, expr);
0258     bool ok;
0259     if (visible) {
0260         ok = addField(field);
0261     } else {
0262         ok = addInvisibleField(field);
0263     }
0264     if (!ok) {
0265         delete field;
0266     }
0267     d->ownedExpressionFields.append(field);
0268     return ok;
0269 }
0270 
0271 bool KDbQuerySchema::addExpression(const KDbExpression& expr)
0272 {
0273     return addExpressionInternal(expr, true);
0274 }
0275 
0276 bool KDbQuerySchema::addInvisibleExpression(const KDbExpression& expr)
0277 {
0278     return addExpressionInternal(expr, false);
0279 }
0280 
0281 bool KDbQuerySchema::isColumnVisible(int position) const
0282 {
0283     return (position < fieldCount()) ? d->visibility.testBit(position) : false;
0284 }
0285 
0286 void KDbQuerySchema::setColumnVisible(int position, bool visible)
0287 {
0288     if (position < fieldCount())
0289         d->visibility.setBit(position, visible);
0290 }
0291 
0292 bool KDbQuerySchema::addAsteriskInternal(KDbQueryAsterisk *asterisk, bool visible)
0293 {
0294     if (!asterisk) {
0295         return false;
0296     }
0297     //make unique name
0298     asterisk->setName((asterisk->table() ? (asterisk->table()->name() + QLatin1String(".*"))
0299                                          : QString(QLatin1Char('*')))
0300                        + QString::number(asterisks()->count()));
0301     return visible ? addField(asterisk) : addInvisibleField(asterisk);
0302 }
0303 
0304 bool KDbQuerySchema::addAsterisk(KDbQueryAsterisk *asterisk)
0305 {
0306     return addAsteriskInternal(asterisk, true);
0307 }
0308 
0309 bool KDbQuerySchema::addInvisibleAsterisk(KDbQueryAsterisk *asterisk)
0310 {
0311     return addAsteriskInternal(asterisk, false);
0312 }
0313 
0314 QDebug operator<<(QDebug dbg, const KDbConnectionAndQuerySchema &connectionAndSchema)
0315 {
0316     KDbConnection* conn = std::get<0>(connectionAndSchema);
0317     const KDbQuerySchema& query = std::get<1>(connectionAndSchema);
0318     //fields
0319     KDbTableSchema *mt = query.masterTable();
0320     dbg.nospace() << "QUERY";
0321     dbg.space() << static_cast<const KDbObject&>(query) << '\n';
0322     dbg.nospace() << " - MASTERTABLE=" << (mt ? mt->name() : QLatin1String("<NULL>"))
0323         << "\n - COLUMNS:\n";
0324     if (query.fieldCount() > 0)
0325         dbg.nospace() << static_cast<const KDbFieldList&>(query) << '\n';
0326     else
0327         dbg.nospace() << "<NONE>\n";
0328 
0329     if (query.fieldCount() == 0)
0330         dbg.nospace() << " - NO FIELDS\n";
0331     else
0332         dbg.nospace() << " - FIELDS EXPANDED (";
0333 
0334     int fieldsExpandedCount = 0;
0335     bool first;
0336     if (query.fieldCount() > 0) {
0337         const KDbQueryColumnInfo::Vector fe(query.fieldsExpanded(conn));
0338         fieldsExpandedCount = fe.size();
0339         dbg.nospace() << fieldsExpandedCount << "):\n";
0340         first = true;
0341         for (int i = 0; i < fieldsExpandedCount; i++) {
0342             KDbQueryColumnInfo *ci = fe[i];
0343             if (first)
0344                 first = false;
0345             else
0346                 dbg.nospace() << ",\n";
0347             dbg.nospace() << *ci;
0348         }
0349         dbg.nospace() << '\n';
0350     }
0351 
0352     //it's safer to delete fieldsExpanded for now
0353     // (debugString() could be called before all fields are added)
0354 
0355     //bindings
0356     dbg.nospace() << " - BINDINGS:\n";
0357     bool bindingsExist = false;
0358     for (int i = 0; i < query.fieldCount(); i++) {
0359         const int tablePos = query.tableBoundToColumn(i);
0360         if (tablePos >= 0) {
0361             const QString tAlias(query.tableAlias(tablePos));
0362             if (!tAlias.isEmpty()) {
0363                 bindingsExist = true;
0364                 dbg.space() << "FIELD";
0365                 dbg.space() << static_cast<const KDbFieldList&>(query).field(i)->name();
0366                 dbg.space() << "USES ALIAS";
0367                 dbg.space() << tAlias;
0368                 dbg.space() << "OF TABLE";
0369                 dbg.space() << query.tables()->at(tablePos)->name() << '\n';
0370             }
0371         }
0372     }
0373     if (!bindingsExist) {
0374         dbg.nospace() << "<NONE>\n";
0375     }
0376 
0377     //tables
0378     dbg.nospace() << " - TABLES:\n";
0379     first = true;
0380     foreach(KDbTableSchema *table, *query.tables()) {
0381         if (first)
0382             first = false;
0383         else
0384             dbg.nospace() << ",";
0385         dbg.space() << table->name();
0386     }
0387     if (query.tables()->isEmpty())
0388         dbg.nospace() << "<NONE>";
0389 
0390     //aliases
0391     dbg.nospace() << "\n - COLUMN ALIASES:\n";
0392     if (query.columnAliasesCount() == 0) {
0393         dbg.nospace() << "<NONE>\n";
0394     }
0395     else {
0396         int i = -1;
0397         foreach(KDbField *f, *query.fields()) {
0398             i++;
0399             const QString alias(query.columnAlias(i));
0400             if (!alias.isEmpty()) {
0401                 dbg.nospace() << QString::fromLatin1("FIELD #%1:").arg(i);
0402                 dbg.space() << (f->name().isEmpty()
0403                     ? QLatin1String("<NONAME>") : f->name()) << " -> " << alias << '\n';
0404             }
0405         }
0406     }
0407 
0408     dbg.nospace() << "- TABLE ALIASES:\n";
0409     if (query.tableAliasesCount() == 0) {
0410         dbg.nospace() << "<NONE>\n";
0411     }
0412     else {
0413         int i = -1;
0414         foreach(KDbTableSchema* table, *query.tables()) {
0415             i++;
0416             const QString alias(query.tableAlias(i));
0417             if (!alias.isEmpty()) {
0418                 dbg.nospace() << QString::fromLatin1("table #%1:").arg(i);
0419                 dbg.space() << (table->name().isEmpty()
0420                     ? QLatin1String("<NONAME>") : table->name()) << " -> " << alias << '\n';
0421             }
0422         }
0423     }
0424     if (!query.whereExpression().isNull()) {
0425         dbg.nospace() << " - WHERE EXPRESSION:\n" << query.whereExpression() << '\n';
0426     }
0427     dbg.nospace() << qPrintable(QString::fromLatin1(" - ORDER BY (%1):\n").arg(query.orderByColumnList()->count()));
0428     if (query.orderByColumnList()->isEmpty()) {
0429         dbg.nospace() << "<NONE>\n";
0430     } else {
0431         dbg.nospace() << *query.orderByColumnList();
0432     }
0433     return dbg.nospace();
0434 }
0435 
0436 KDbTableSchema* KDbQuerySchema::masterTable() const
0437 {
0438     if (d->masterTable)
0439         return d->masterTable;
0440     if (d->tables.isEmpty())
0441         return nullptr;
0442 
0443     //try to find master table if there's only one table (with possible aliasses)
0444     QString tableNameLower;
0445     int num = -1;
0446     foreach(KDbTableSchema *table, d->tables) {
0447         num++;
0448         if (!tableNameLower.isEmpty() && table->name().toLower() != tableNameLower) {
0449             //two or more different tables
0450             return nullptr;
0451         }
0452         tableNameLower = tableAlias(num);
0453     }
0454     return d->tables.first();
0455 }
0456 
0457 void KDbQuerySchema::setMasterTable(KDbTableSchema *table)
0458 {
0459     if (table)
0460         d->masterTable = table;
0461 }
0462 
0463 QList<KDbTableSchema*>* KDbQuerySchema::tables() const
0464 {
0465     return &d->tables;
0466 }
0467 
0468 void KDbQuerySchema::addTable(KDbTableSchema *table, const QString& alias)
0469 {
0470     querySchemaDebug() << (void *)table << "alias=" << alias;
0471     if (!table)
0472         return;
0473 
0474     // only append table if: it has alias or it has no alias but there is no such table on the list
0475     if (alias.isEmpty() && d->tables.contains(table)) {
0476         int num = -1;
0477         foreach(KDbTableSchema *t, d->tables) {
0478             num++;
0479             if (0 == t->name().compare(table->name(), Qt::CaseInsensitive)) {
0480                 if (tableAlias(num).isEmpty()) {
0481                     querySchemaDebug() << "table" << table->name() << "without alias already added";
0482                     return;
0483                 }
0484             }
0485         }
0486     }
0487     d->tables.append(table);
0488     if (!alias.isEmpty())
0489         setTableAlias(d->tables.count() - 1, alias);
0490 }
0491 
0492 void KDbQuerySchema::removeTable(KDbTableSchema *table)
0493 {
0494     if (!table)
0495         return;
0496     if (d->masterTable == table)
0497         d->masterTable = nullptr;
0498     d->tables.removeAt(d->tables.indexOf(table));
0499 //! @todo remove fields!
0500 }
0501 
0502 KDbTableSchema* KDbQuerySchema::table(const QString& tableName) const
0503 {
0504 //! @todo maybe use tables_byname?
0505     foreach(KDbTableSchema *table, d->tables) {
0506         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
0507             return table;
0508         }
0509     }
0510     return nullptr;
0511 }
0512 
0513 bool KDbQuerySchema::contains(KDbTableSchema *table) const
0514 {
0515     return d->tables.contains(table);
0516 }
0517 
0518 KDbField* KDbQuerySchema::findTableField(const QString &fieldOrTableAndFieldName) const
0519 {
0520     QString tableName, fieldName;
0521     if (!KDb::splitToTableAndFieldParts(fieldOrTableAndFieldName,
0522                                         &tableName, &fieldName,
0523                                         KDb::SetFieldNameIfNoTableName)) {
0524         return nullptr;
0525     }
0526     if (tableName.isEmpty()) {
0527         foreach(KDbTableSchema *table, d->tables) {
0528             if (table->field(fieldName))
0529                 return table->field(fieldName);
0530         }
0531         return nullptr;
0532     }
0533     KDbTableSchema *tableSchema = table(tableName);
0534     if (!tableSchema)
0535         return nullptr;
0536     return tableSchema->field(fieldName);
0537 }
0538 
0539 int KDbQuerySchema::columnAliasesCount() const
0540 {
0541     return d->columnAliasesCount();
0542 }
0543 
0544 QString KDbQuerySchema::columnAlias(int position) const
0545 {
0546     return d->columnAlias(position);
0547 }
0548 
0549 bool KDbQuerySchema::hasColumnAlias(int position) const
0550 {
0551     return d->hasColumnAlias(position);
0552 }
0553 
0554 bool KDbQuerySchema::setColumnAlias(int position, const QString& alias)
0555 {
0556     if (position >= fieldCount()) {
0557         kdbWarning() << "position"  << position << "out of range!";
0558         return false;
0559     }
0560     const QString fixedAlias(alias.trimmed());
0561     KDbField *f = KDbFieldList::field(position);
0562     if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) {
0563         kdbWarning() << "position" << position << "could not remove alias when no name is specified for expression column!";
0564         return false;
0565     }
0566     return d->setColumnAlias(position, fixedAlias);
0567 }
0568 
0569 int KDbQuerySchema::tableAliasesCount() const
0570 {
0571     return d->tableAliases.count();
0572 }
0573 
0574 QString KDbQuerySchema::tableAlias(int position) const
0575 {
0576     return d->tableAliases.value(position);
0577 }
0578 
0579 QString KDbQuerySchema::tableAlias(const QString& tableName) const
0580 {
0581     const int pos = tablePosition(tableName);
0582     if (pos == -1) {
0583         return QString();
0584     }
0585     return d->tableAliases.value(pos);
0586 }
0587 
0588 QString KDbQuerySchema::tableAliasOrName(const QString& tableName) const
0589 {
0590     const int pos = tablePosition(tableName);
0591     if (pos == -1) {
0592         return QString();
0593     }
0594     return KDb::iifNotEmpty(d->tableAliases.value(pos), tableName);
0595 }
0596 
0597 int KDbQuerySchema::tablePositionForAlias(const QString& name) const
0598 {
0599     return d->tablePositionForAlias(name);
0600 }
0601 
0602 int KDbQuerySchema::tablePosition(const QString& tableName) const
0603 {
0604     int num = -1;
0605     foreach(KDbTableSchema* table, d->tables) {
0606         num++;
0607         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
0608             return num;
0609         }
0610     }
0611     return -1;
0612 }
0613 
0614 QList<int> KDbQuerySchema::tablePositions(const QString& tableName) const
0615 {
0616     QList<int> result;
0617     int num = -1;
0618     foreach(KDbTableSchema* table, d->tables) {
0619         num++;
0620         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
0621             result += num;
0622         }
0623     }
0624     return result;
0625 }
0626 
0627 bool KDbQuerySchema::hasTableAlias(int position) const
0628 {
0629     return d->tableAliases.contains(position);
0630 }
0631 
0632 bool KDbQuerySchema::hasTableAlias(const QString &name) const
0633 {
0634     return d->tablePositionForAlias(name) != -1;
0635 }
0636 
0637 int KDbQuerySchema::columnPositionForAlias(const QString& name) const
0638 {
0639     return d->columnPositionForAlias(name);
0640 }
0641 
0642 bool KDbQuerySchema::hasColumnAlias(const QString &name) const
0643 {
0644     return d->columnPositionForAlias(name) != -1;
0645 }
0646 
0647 bool KDbQuerySchema::setTableAlias(int position, const QString& alias)
0648 {
0649     if (position >= d->tables.count()) {
0650         kdbWarning() << "position"  << position << "out of range!";
0651         return false;
0652     }
0653     const QString fixedAlias(alias.trimmed());
0654     if (fixedAlias.isEmpty()) {
0655         const QString oldAlias(d->tableAliases.take(position));
0656         if (!oldAlias.isEmpty()) {
0657             d->removeTablePositionForAlias(oldAlias);
0658         }
0659         return true;
0660     }
0661     return d->setTableAlias(position, fixedAlias);
0662 }
0663 
0664 QList<KDbRelationship*>* KDbQuerySchema::relationships() const
0665 {
0666     return &d->relations;
0667 }
0668 
0669 KDbField::List* KDbQuerySchema::asterisks() const
0670 {
0671     return &d->asterisks;
0672 }
0673 
0674 KDbEscapedString KDbQuerySchema::statement() const
0675 {
0676     return d->sql;
0677 }
0678 
0679 void KDbQuerySchema::setStatement(const KDbEscapedString& sql)
0680 {
0681     d->sql = sql;
0682 }
0683 
0684 const KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier,
0685                                       ExpandMode mode) const
0686 {
0687     KDbQueryColumnInfo *ci = columnInfo(conn, identifier, mode);
0688     return ci ? ci->field() : nullptr;
0689 }
0690 
0691 KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier, ExpandMode mode)
0692 {
0693     return const_cast<KDbField *>(
0694         static_cast<const KDbQuerySchema *>(this)->field(conn, identifier, mode));
0695 }
0696 
0697 KDbField* KDbQuerySchema::field(int id)
0698 {
0699     return KDbFieldList::field(id);
0700 }
0701 
0702 const KDbField* KDbQuerySchema::field(int id) const
0703 {
0704     return KDbFieldList::field(id);
0705 }
0706 
0707 KDbQueryColumnInfo *KDbQuerySchema::columnInfo(KDbConnection *conn, const QString &identifier,
0708                                                ExpandMode mode) const
0709 {
0710     const KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
0711     return mode == ExpandMode::Expanded ? cache->columnInfosByNameExpanded.value(identifier)
0712                                         : cache->columnInfosByName.value(identifier);
0713 }
0714 
0715 KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpandedInternal(
0716         KDbConnection *conn, FieldsExpandedMode mode, bool onlyVisible) const
0717 {
0718     if (!conn) {
0719         kdbWarning() << "Connection required";
0720         return KDbQueryColumnInfo::Vector();
0721     }
0722     KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
0723     const KDbQueryColumnInfo::Vector *realFieldsExpanded
0724         = onlyVisible ? &cache->visibleFieldsExpanded : &cache->fieldsExpanded;
0725     if (mode == FieldsExpandedMode::WithInternalFields
0726         || mode == FieldsExpandedMode::WithInternalFieldsAndRecordId)
0727     {
0728         //a ref to a proper pointer (as we cache the vector for two cases)
0729         KDbQueryColumnInfo::Vector& tmpFieldsExpandedWithInternal =
0730             (mode == FieldsExpandedMode::WithInternalFields) ?
0731                 (onlyVisible ? cache->visibleFieldsExpandedWithInternal : cache->fieldsExpandedWithInternal)
0732               : (onlyVisible ? cache->visibleFieldsExpandedWithInternalAndRecordId : cache->fieldsExpandedWithInternalAndRecordId);
0733         //special case
0734         if (tmpFieldsExpandedWithInternal.isEmpty()) {
0735             //glue expanded and internal fields and cache it
0736             const int internalFieldCount = cache->internalFields.size();
0737             const int fieldsExpandedVectorSize = realFieldsExpanded->size();
0738             const int size = fieldsExpandedVectorSize + internalFieldCount
0739                 + ((mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) ? 1 : 0) /*ROWID*/;
0740             tmpFieldsExpandedWithInternal.resize(size);
0741             for (int i = 0; i < fieldsExpandedVectorSize; ++i) {
0742                 tmpFieldsExpandedWithInternal[i] = realFieldsExpanded->at(i);
0743             }
0744             if (internalFieldCount > 0) {
0745                 for (int i = 0; i < internalFieldCount; ++i) {
0746                     KDbQueryColumnInfo *info = cache->internalFields[i];
0747                     tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + i] = info;
0748                 }
0749             }
0750             if (mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) {
0751                 if (!d->fakeRecordIdField) {
0752                     d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), KDbField::BigInteger);
0753                     d->fakeRecordIdCol = new KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true);
0754                     d->fakeRecordIdCol->d->querySchema = this;
0755                     d->fakeRecordIdCol->d->connection = conn;
0756                 }
0757                 tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + internalFieldCount] = d->fakeRecordIdCol;
0758             }
0759         }
0760         return tmpFieldsExpandedWithInternal;
0761     }
0762 
0763     if (mode == FieldsExpandedMode::Default) {
0764         return *realFieldsExpanded;
0765     }
0766 
0767     //mode == Unique:
0768     QSet<QString> columnsAlreadyFound;
0769     const int fieldsExpandedCount(realFieldsExpanded->count());
0770     KDbQueryColumnInfo::Vector result(fieldsExpandedCount);   //initial size is set
0771     //compute unique list
0772     int uniqueListCount = 0;
0773     for (int i = 0; i < fieldsExpandedCount; i++) {
0774         KDbQueryColumnInfo *ci = realFieldsExpanded->at(i);
0775         if (!columnsAlreadyFound.contains(ci->aliasOrName())) {
0776             columnsAlreadyFound.insert(ci->aliasOrName());
0777             result[uniqueListCount++] = ci;
0778         }
0779     }
0780     result.resize(uniqueListCount); //update result size
0781     return result;
0782 }
0783 
0784 KDbQueryColumnInfo::Vector KDbQuerySchema::internalFields(KDbConnection *conn) const
0785 {
0786     KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
0787     return cache->internalFields;
0788 }
0789 
0790 KDbQueryColumnInfo* KDbQuerySchema::expandedOrInternalField(KDbConnection *conn, int index) const
0791 {
0792     return fieldsExpanded(conn, FieldsExpandedMode::WithInternalFields).value(index);
0793 }
0794 
0795 inline static QString lookupColumnKey(KDbField *foreignField, KDbField* field)
0796 {
0797     QString res;
0798     if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns
0799         res = field->table()->name() + QLatin1Char('.');
0800     return res + field->name() + QLatin1Char('_') + foreignField->table()->name()
0801                + QLatin1Char('.') + foreignField->name();
0802 }
0803 
0804 KDbQuerySchemaFieldsExpanded *KDbQuerySchema::computeFieldsExpanded(KDbConnection *conn) const
0805 {
0806     KDbQuerySchemaFieldsExpanded *cache = conn->d->fieldsExpanded(this);
0807     if (cache) {
0808         return cache;
0809     }
0810     cache = new KDbQuerySchemaFieldsExpanded;
0811     QScopedPointer<KDbQuerySchemaFieldsExpanded> guard(cache);
0812 
0813     //collect all fields in a list (not a vector yet, because we do not know its size)
0814     KDbQueryColumnInfo::List list; //temporary
0815     KDbQueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields
0816     QHash<KDbQueryColumnInfo*, bool> columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName
0817     int i = 0;
0818     int numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field
0819     int fieldPosition = -1;
0820     for (KDbField *f : *fields()) {
0821         fieldPosition++;
0822         if (f->isQueryAsterisk()) {
0823             if (static_cast<KDbQueryAsterisk*>(f)->isSingleTableAsterisk()) {
0824                 const KDbField::List *ast_fields = static_cast<KDbQueryAsterisk*>(f)->table()->fields();
0825                 foreach(KDbField *ast_f, *ast_fields) {
0826                     KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, QString()/*no field for asterisk!*/,
0827                             isColumnVisible(fieldPosition));
0828                     ci->d->querySchema = this;
0829                     ci->d->connection = conn;
0830                     list.append(ci);
0831                     querySchemaDebug() << "caching (unexpanded) columns order:" << *ci
0832                                        << "at position" << fieldPosition;
0833                     cache->columnsOrder.insert(ci, fieldPosition);
0834                 }
0835             } else {//all-tables asterisk: iterate through table list
0836                 foreach(KDbTableSchema *table, d->tables) {
0837                     //add all fields from this table
0838                     const KDbField::List *tab_fields = table->fields();
0839                     foreach(KDbField *tab_f, *tab_fields) {
0840 //! @todo (js): perhaps not all fields should be appended here
0841 //      d->detailedVisibility += isFieldVisible(fieldPosition);
0842 //      list.append(tab_f);
0843                         KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, QString()/*no field for asterisk!*/,
0844                                 isColumnVisible(fieldPosition));
0845                         ci->d->querySchema = this;
0846                         ci->d->connection = conn;
0847                         list.append(ci);
0848                         querySchemaDebug() << "caching (unexpanded) columns order:" << *ci
0849                                            << "at position" << fieldPosition;
0850                         cache->columnsOrder.insert(ci, fieldPosition);
0851                     }
0852                 }
0853             }
0854         } else {
0855             //a single field
0856             KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition));
0857             ci->d->querySchema = this;
0858             ci->d->connection = conn;
0859             list.append(ci);
0860             columnInfosOutsideAsterisks.insert(ci, true);
0861             querySchemaDebug() << "caching (unexpanded) column's order:" << *ci << "at position"
0862                                << fieldPosition;
0863             cache->columnsOrder.insert(ci, fieldPosition);
0864             cache->columnsOrderWithoutAsterisks.insert(ci, fieldPosition);
0865 
0866             //handle lookup field schema
0867             KDbLookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema(*f) : nullptr;
0868             if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0)
0869                 continue;
0870             // Lookup field schema found:
0871             // Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
0872             // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
0873             // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
0874             KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource();
0875             if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) {
0876                 KDbTableSchema *lookupTable = conn->tableSchema(recordSource.name());
0877                 KDbFieldList* visibleColumns = nullptr;
0878                 KDbField *boundField = nullptr;
0879                 if (lookupTable
0880                         && lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
0881                         && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))
0882                         && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) {
0883                     KDbField *visibleColumn = nullptr;
0884                     // for single visible column, just add it as-is
0885                     if (visibleColumns->fieldCount() == 1) {
0886                         visibleColumn = visibleColumns->fields()->first();
0887                     } else {
0888                         // for multiple visible columns, build an expression column
0889                         // (the expression object will be owned by column info)
0890                         visibleColumn = new KDbField();
0891                         visibleColumn->setName(
0892                             QString::fromLatin1("[multiple_visible_fields_%1]")
0893                             .arg(++numberOfColumnsWithMultipleVisibleFields));
0894                         visibleColumn->setExpression(
0895                             KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
0896                         cache->ownedVisibleFields.append(visibleColumn);   // remember to delete later
0897                     }
0898 
0899                     KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo(
0900                         visibleColumn, QString(), true /*visible*/, ci /*foreign*/);
0901                     lookupCi->d->querySchema = this;
0902                     lookupCi->d->connection = conn;
0903                     lookup_list.append(lookupCi);
0904                     /*
0905                               //add visibleField to the list of SELECTed fields if it is not yes present there
0906                               if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
0907                                 if (!table( visibleField->table()->name() )) {
0908                                 }
0909                                 if (!sql.isEmpty())
0910                                   sql += QString::fromLatin1(", ");
0911                                 sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
0912                                   + escapeIdentifier(visibleField->name(), drvEscaping));
0913                               }*/
0914                 }
0915                 delete visibleColumns;
0916             } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) {
0917                 KDbQuerySchema *lookupQuery = conn->querySchema(recordSource.name());
0918                 if (!lookupQuery)
0919                     continue;
0920                 const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(
0921                     lookupQuery->fieldsExpanded(conn));
0922                 if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count())
0923                     continue;
0924                 KDbQueryColumnInfo *boundColumnInfo = nullptr;
0925                 if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn())))
0926                     continue;
0927                 KDbField *boundField = boundColumnInfo->field();
0928                 if (!boundField)
0929                     continue;
0930                 const QList<int> visibleColumns(lookupFieldSchema->visibleColumns());
0931                 bool ok = true;
0932                 // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1]
0933                 foreach(int visibleColumn, visibleColumns) {
0934                     if (visibleColumn >= lookupQueryFieldsExpanded.count()) {
0935                         ok = false;
0936                         break;
0937                     }
0938                 }
0939                 if (!ok)
0940                     continue;
0941                 KDbField *visibleColumn = nullptr;
0942                 // for single visible column, just add it as-is
0943                 if (visibleColumns.count() == 1) {
0944                     visibleColumn = lookupQueryFieldsExpanded.value(visibleColumns.first())->field();
0945                 } else {
0946                     // for multiple visible columns, build an expression column
0947                     // (the expression object will be owned by column info)
0948                     visibleColumn = new KDbField();
0949                     visibleColumn->setName(
0950                         QString::fromLatin1("[multiple_visible_fields_%1]")
0951                         .arg(++numberOfColumnsWithMultipleVisibleFields));
0952                     visibleColumn->setExpression(
0953                         KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
0954                     cache->ownedVisibleFields.append(visibleColumn);   // remember to delete later
0955                 }
0956 
0957                 KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo(
0958                     visibleColumn, QString(), true /*visible*/, ci /*foreign*/);
0959                 lookupCi->d->querySchema = this;
0960                 lookupCi->d->connection = conn;
0961                 lookup_list.append(lookupCi);
0962                 /*
0963                         //add visibleField to the list of SELECTed fields if it is not yes present there
0964                         if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
0965                           if (!table( visibleField->table()->name() )) {
0966                           }
0967                           if (!sql.isEmpty())
0968                             sql += QString::fromLatin1(", ");
0969                           sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
0970                             + escapeIdentifier(visibleField->name(), drvEscaping));
0971                         }*/
0972             }
0973         }
0974     }
0975     //prepare clean vector for expanded list, and a map for order information
0976     cache->fieldsExpanded.resize(list.count());
0977     cache->visibleFieldsExpanded.resize(list.count());
0978 
0979     /*fill (based on prepared 'list' and 'lookup_list'):
0980      -the vector
0981      -the map
0982      -"fields by name" dictionary
0983     */
0984     i = -1;
0985     int visibleIndex = -1;
0986     foreach(KDbQueryColumnInfo* ci, list) {
0987         i++;
0988         cache->fieldsExpanded[i] = ci;
0989         if (ci->isVisible()) {
0990             ++visibleIndex;
0991             cache->visibleFieldsExpanded[visibleIndex] = ci;
0992         }
0993         cache->columnsOrderExpanded.insert(ci, i);
0994         //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded
0995         if (!ci->alias().isEmpty()) {
0996             //store alias and table.alias
0997             if (!cache->columnInfosByNameExpanded.contains(ci->alias())) {
0998                 cache->columnInfosByNameExpanded.insert(ci->alias(), ci);
0999             }
1000             QString tableAndAlias(ci->alias());
1001             if (ci->field()->table())
1002                 tableAndAlias.prepend(ci->field()->table()->name() + QLatin1Char('.'));
1003             if (!cache->columnInfosByNameExpanded.contains(tableAndAlias)) {
1004                 cache->columnInfosByNameExpanded.insert(tableAndAlias, ci);
1005             }
1006             //the same for "unexpanded" list
1007             if (columnInfosOutsideAsterisks.contains(ci)) {
1008                 if (!cache->columnInfosByName.contains(ci->alias())) {
1009                     cache->columnInfosByName.insert(ci->alias(), ci);
1010                 }
1011                 if (!cache->columnInfosByName.contains(tableAndAlias)) {
1012                     cache->columnInfosByName.insert(tableAndAlias, ci);
1013                 }
1014             }
1015         } else {
1016             //no alias: store name and table.name
1017             if (!cache->columnInfosByNameExpanded.contains(ci->field()->name())) {
1018                 cache->columnInfosByNameExpanded.insert(ci->field()->name(), ci);
1019             }
1020             QString tableAndName(ci->field()->name());
1021             if (ci->field()->table())
1022                 tableAndName.prepend(ci->field()->table()->name() + QLatin1Char('.'));
1023             if (!cache->columnInfosByNameExpanded.contains(tableAndName)) {
1024                 cache->columnInfosByNameExpanded.insert(tableAndName, ci);
1025             }
1026             //the same for "unexpanded" list
1027             if (columnInfosOutsideAsterisks.contains(ci)) {
1028                 if (!cache->columnInfosByName.contains(ci->field()->name())) {
1029                     cache->columnInfosByName.insert(ci->field()->name(), ci);
1030                 }
1031                 if (!cache->columnInfosByName.contains(tableAndName)) {
1032                     cache->columnInfosByName.insert(tableAndName, ci);
1033                 }
1034             }
1035         }
1036     }
1037     cache->visibleFieldsExpanded.resize(visibleIndex + 1);
1038 
1039     //remove duplicates for lookup fields
1040     QHash<QString, int> lookup_dict; //used to fight duplicates and to update KDbQueryColumnInfo::indexForVisibleLookupValue()
1041     // (a mapping from table.name string to int* lookupFieldIndex
1042     i = 0;
1043     for (QMutableListIterator<KDbQueryColumnInfo*> it(lookup_list); it.hasNext();) {
1044         KDbQueryColumnInfo* ci = it.next();
1045         const QString key(lookupColumnKey(ci->foreignColumn()->field(), ci->field()));
1046         if (lookup_dict.contains(key)) {
1047             // this table.field is already fetched by this query
1048             it.remove();
1049             delete ci;
1050         } else {
1051             lookup_dict.insert(key, i);
1052             i++;
1053         }
1054     }
1055 
1056     //create internal expanded list with lookup fields
1057     cache->internalFields.resize(lookup_list.count());
1058     i = -1;
1059     foreach(KDbQueryColumnInfo *ci, lookup_list) {
1060         i++;
1061         //add it to the internal list
1062         cache->internalFields[i] = ci;
1063         cache->columnsOrderExpanded.insert(ci, list.count() + i);
1064     }
1065 
1066     //update KDbQueryColumnInfo::indexForVisibleLookupValue() cache for columns
1067     numberOfColumnsWithMultipleVisibleFields = 0;
1068     for (i = 0; i < cache->fieldsExpanded.size(); i++) {
1069         KDbQueryColumnInfo* ci = cache->fieldsExpanded[i];
1070 //! @todo KDbQuerySchema itself will also support lookup fields...
1071         KDbLookupFieldSchema *lookupFieldSchema
1072             = ci->field()->table() ? ci->field()->table()->lookupFieldSchema(*ci->field()) : nullptr;
1073         if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0)
1074             continue;
1075         const KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource();
1076         if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) {
1077             KDbTableSchema *lookupTable = conn->tableSchema(recordSource.name());
1078             KDbFieldList* visibleColumns = nullptr;
1079             if (lookupTable
1080                     && lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
1081                     && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))) {
1082                 // for single visible column, just add it as-is
1083                 if (visibleColumns->fieldCount() == 1) {
1084                     KDbField *visibleColumn = visibleColumns->fields()->first();
1085                     const QString key(lookupColumnKey(ci->field(), visibleColumn));
1086                     int index = lookup_dict.value(key, -99);
1087                     if (index != -99)
1088                         ci->setIndexForVisibleLookupValue(cache->fieldsExpanded.size() + index);
1089                 } else {
1090                     const QString key(QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3")
1091                                       .arg(++numberOfColumnsWithMultipleVisibleFields)
1092                                       .arg(ci->field()->table()->name(), ci->field()->name()));
1093                     int index = lookup_dict.value(key, -99);
1094                     if (index != -99)
1095                         ci->setIndexForVisibleLookupValue(cache->fieldsExpanded.size() + index);
1096                 }
1097             }
1098             delete visibleColumns;
1099         } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) {
1100             KDbQuerySchema *lookupQuery = conn->querySchema(recordSource.name());
1101             if (!lookupQuery)
1102                 continue;
1103             const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(
1104                 lookupQuery->fieldsExpanded(conn));
1105             if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count())
1106                 continue;
1107             KDbQueryColumnInfo *boundColumnInfo = nullptr;
1108             if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn())))
1109                 continue;
1110             KDbField *boundField = boundColumnInfo->field();
1111             if (!boundField)
1112                 continue;
1113             const QList<int> visibleColumns(lookupFieldSchema->visibleColumns());
1114             // for single visible column, just add it as-is
1115             if (visibleColumns.count() == 1) {
1116                 if (lookupQueryFieldsExpanded.count() > visibleColumns.first()) { // sanity check
1117                     KDbField *visibleColumn = lookupQueryFieldsExpanded.at(visibleColumns.first())->field();
1118                     const QString key(lookupColumnKey(ci->field(), visibleColumn));
1119                     int index = lookup_dict.value(key, -99);
1120                     if (index != -99)
1121                         ci->setIndexForVisibleLookupValue(cache->fieldsExpanded.size() + index);
1122                 }
1123             } else {
1124                 const QString key(QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3")
1125                                   .arg(++numberOfColumnsWithMultipleVisibleFields)
1126                                   .arg(ci->field()->table()->name(), ci->field()->name()));
1127                 int index = lookup_dict.value(key, -99);
1128                 if (index != -99)
1129                     ci->setIndexForVisibleLookupValue(cache->fieldsExpanded.size() + index);
1130             }
1131         } else {
1132             kdbWarning() << "unsupported record source type" << recordSource.typeName();
1133         }
1134     }
1135     if (d->recentConnection != conn) {
1136         if (d->recentConnection) {
1137             // connection changed: remove old cache
1138             d->recentConnection->d->removeFieldsExpanded(this);
1139         }
1140         d->recentConnection = conn;
1141     }
1142     conn->d->insertFieldsExpanded(this, guard.take());
1143     return cache;
1144 }
1145 
1146 QHash<KDbQueryColumnInfo*, int> KDbQuerySchema::columnsOrder(KDbConnection *conn,
1147                                                              ColumnsOrderMode mode) const
1148 {
1149     KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
1150     if (mode == ColumnsOrderMode::UnexpandedList) {
1151         return cache->columnsOrder;
1152     } else if (mode == ColumnsOrderMode::UnexpandedListWithoutAsterisks) {
1153         return cache->columnsOrderWithoutAsterisks;
1154     }
1155     return cache->columnsOrderExpanded;
1156 }
1157 
1158 QVector<int> KDbQuerySchema::pkeyFieldsOrder(KDbConnection *conn) const
1159 {
1160     if (d->pkeyFieldsOrder)
1161         return *d->pkeyFieldsOrder;
1162 
1163     KDbTableSchema *tbl = masterTable();
1164     if (!tbl || !tbl->primaryKey())
1165         return QVector<int>();
1166 
1167     //get order of PKEY fields (e.g. for records updating or inserting )
1168     KDbIndexSchema *pkey = tbl->primaryKey();
1169     querySchemaDebug() << *pkey;
1170     d->pkeyFieldsOrder = new QVector<int>(pkey->fieldCount(), -1);
1171 
1172     d->pkeyFieldCount = 0;
1173     const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn));
1174     const int fCount = fieldsExpanded.count();
1175     for (int i = 0; i < fCount; i++) {
1176         const KDbQueryColumnInfo *fi = fieldsExpanded[i];
1177         const int fieldIndex = fi->field()->table() == tbl ? pkey->indexOf(*fi->field()) : -1;
1178         if (fieldIndex != -1 /* field found in PK */
1179             && d->pkeyFieldsOrder->at(fieldIndex) == -1 /* first time */)
1180         {
1181             querySchemaDebug() << "FIELD" << fi->field()->name() << "IS IN PKEY AT POSITION #"
1182                                << fieldIndex;
1183             (*d->pkeyFieldsOrder)[fieldIndex] = i;
1184             d->pkeyFieldCount++;
1185         }
1186     }
1187     querySchemaDebug() << d->pkeyFieldCount << " OUT OF " << pkey->fieldCount()
1188                        << " PKEY'S FIELDS FOUND IN QUERY " << name();
1189     return *d->pkeyFieldsOrder;
1190 }
1191 
1192 int KDbQuerySchema::pkeyFieldCount(KDbConnection *conn)
1193 {
1194     (void)pkeyFieldsOrder(conn); /* rebuild information */
1195     return d->pkeyFieldCount;
1196 }
1197 
1198 KDbRelationship* KDbQuerySchema::addRelationship(KDbField *field1, KDbField *field2)
1199 {
1200 //@todo: find existing global db relationships
1201     KDbRelationship *r = new KDbRelationship(this, field1, field2);
1202     if (r->isEmpty()) {
1203         delete r;
1204         return nullptr;
1205     }
1206 
1207     d->relations.append(r);
1208     return r;
1209 }
1210 
1211 KDbQueryColumnInfo::List* KDbQuerySchema::autoIncrementFields(KDbConnection *conn) const
1212 {
1213     if (!d->autoincFields) {
1214         d->autoincFields = new KDbQueryColumnInfo::List();
1215     }
1216     KDbTableSchema *mt = masterTable();
1217     if (!mt) {
1218         kdbWarning() << "no master table!";
1219         return d->autoincFields;
1220     }
1221     if (d->autoincFields->isEmpty()) {//no cache
1222         const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn));
1223         for (int i = 0; i < fieldsExpanded.count(); i++) {
1224             KDbQueryColumnInfo *ci = fieldsExpanded[i];
1225             if (ci->field()->table() == mt && ci->field()->isAutoIncrement()) {
1226                 d->autoincFields->append(ci);
1227             }
1228         }
1229     }
1230     return d->autoincFields;
1231 }
1232 
1233 // static
1234 KDbEscapedString KDbQuerySchema::sqlColumnsList(const KDbQueryColumnInfo::List &infolist,
1235                                                 KDbConnection *conn,
1236                                                 KDb::IdentifierEscapingType escapingType)
1237 {
1238     KDbEscapedString result;
1239     result.reserve(256);
1240     bool start = true;
1241     foreach(KDbQueryColumnInfo* ci, infolist) {
1242         if (!start)
1243             result += ",";
1244         else
1245             start = false;
1246         result += escapeIdentifier(ci->field()->name(), conn, escapingType);
1247     }
1248     return result;
1249 }
1250 
1251 KDbEscapedString KDbQuerySchema::autoIncrementSqlFieldsList(KDbConnection *conn) const
1252 {
1253 //    QWeakPointer<const KDbDriver> driverWeakPointer
1254 //            = DriverManagerInternal::self()->driverWeakPointer(*conn->driver());
1255     if (   /*d->lastUsedDriverForAutoIncrementSQLFieldsList != driverWeakPointer
1256         ||*/ d->autoIncrementSqlFieldsList.isEmpty())
1257     {
1258         d->autoIncrementSqlFieldsList = KDbQuerySchema::sqlColumnsList(*autoIncrementFields(conn), conn);
1259         //d->lastUsedDriverForAutoIncrementSQLFieldsList = driverWeakPointer;
1260     }
1261     return d->autoIncrementSqlFieldsList;
1262 }
1263 
1264 static void setResult(const KDbParseInfoInternal &parseInfo,
1265                       QString *errorMessage, QString *errorDescription)
1266 {
1267     if (errorMessage) {
1268         *errorMessage = parseInfo.errorMessage();
1269     }
1270     if (errorDescription) {
1271         *errorDescription = parseInfo.errorDescription();
1272     }
1273 }
1274 
1275 bool KDbQuerySchema::setWhereExpression(const KDbExpression &expr, QString *errorMessage,
1276                                         QString *errorDescription)
1277 {
1278     KDbExpression newWhereExpr = expr.clone();
1279     KDbParseInfoInternal parseInfo(this);
1280     QString tempErrorMessage;
1281     QString tempErrorDescription;
1282     QString *errorMessagePointer = errorMessage ? errorMessage : &tempErrorMessage;
1283     QString *errorDescriptionPointer
1284         = errorDescription ? errorDescription : &tempErrorDescription;
1285     if (!newWhereExpr.validate(&parseInfo)) {
1286         setResult(parseInfo, errorMessagePointer, errorDescription);
1287         kdbWarning() << "message=" << *errorMessagePointer
1288                      << "description=" << *errorDescriptionPointer;
1289         kdbWarning() << newWhereExpr;
1290         d->whereExpr = KDbExpression();
1291         return false;
1292     }
1293     errorMessagePointer->clear();
1294     errorDescriptionPointer->clear();
1295     KDbQuerySchemaPrivate::setWhereExpressionInternal(this, newWhereExpr);
1296     return true;
1297 }
1298 
1299 bool KDbQuerySchema::addToWhereExpression(KDbField *field, const QVariant &value,
1300                                           KDbToken relation, QString *errorMessage,
1301                                           QString *errorDescription)
1302 {
1303     KDbToken token;
1304     if (value.isNull()) {
1305         token = KDbToken::SQL_NULL;
1306     } else {
1307         const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive
1308         if (KDbField::isIntegerType(type)) {
1309             token = KDbToken::INTEGER_CONST;
1310         } else if (KDbField::isFPNumericType(type)) {
1311             token = KDbToken::REAL_CONST;
1312         } else {
1313             token = KDbToken::CHARACTER_STRING_LITERAL;
1314         }
1315 //! @todo date, time
1316     }
1317 
1318     KDbBinaryExpression newExpr(
1319         KDbConstExpression(token, value),
1320         relation,
1321         KDbVariableExpression((field->table() ? (field->table()->name() + QLatin1Char('.')) : QString()) + field->name())
1322     );
1323     const KDbExpression origWhereExpr = d->whereExpr;
1324     if (!d->whereExpr.isNull()) {
1325         newExpr = KDbBinaryExpression(
1326             d->whereExpr,
1327             KDbToken::AND,
1328             newExpr
1329         );
1330     }
1331     const bool result = setWhereExpression(newExpr, errorMessage, errorDescription);
1332     if (!result) { // revert, setWhereExpression() cleared it
1333         d->whereExpr = origWhereExpr;
1334     }
1335     return result;
1336 }
1337 
1338 /*
1339 void KDbQuerySchema::addToWhereExpression(KDbField *field, const QVariant& value)
1340     switch (value.type()) {
1341     case Int: case UInt: case Bool: case LongLong: case ULongLong:
1342       token = INTEGER_CONST;
1343       break;
1344     case Double:
1345       token = REAL_CONST;
1346       break;
1347     default:
1348       token = CHARACTER_STRING_LITERAL;
1349     }
1350 //! @todo date, time
1351 
1352 */
1353 
1354 KDbExpression KDbQuerySchema::whereExpression() const
1355 {
1356     return d->whereExpr;
1357 }
1358 
1359 void KDbQuerySchema::setOrderByColumnList(const KDbOrderByColumnList& list)
1360 {
1361     delete d->orderByColumnList;
1362     d->orderByColumnList = new KDbOrderByColumnList(list, nullptr, nullptr, nullptr);
1363 // all field names should be found, exit otherwise ..........?
1364 }
1365 
1366 KDbOrderByColumnList* KDbQuerySchema::orderByColumnList()
1367 {
1368     return d->orderByColumnList;
1369 }
1370 
1371 const KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() const
1372 {
1373     return d->orderByColumnList;
1374 }
1375 
1376 QList<KDbQuerySchemaParameter> KDbQuerySchema::parameters(KDbConnection *conn) const
1377 {
1378     QList<KDbQuerySchemaParameter> params;
1379     const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn));
1380     for (int i = 0; i < fieldsExpanded.count(); ++i) {
1381         KDbQueryColumnInfo *ci = fieldsExpanded[i];
1382         if (!ci->field()->expression().isNull()) {
1383             ci->field()->expression().getQueryParameters(&params);
1384         }
1385     }
1386     KDbExpression where = whereExpression();
1387     if (!where.isNull()) {
1388         where.getQueryParameters(&params);
1389     }
1390     return params;
1391 }
1392 
1393 bool KDbQuerySchema::validate(QString *errorMessage, QString *errorDescription)
1394 {
1395     KDbParseInfoInternal parseInfo(this);
1396     foreach(KDbField* f, *fields()) {
1397         if (f->isExpression()) {
1398             if (!f->expression().validate(&parseInfo)) {
1399                 setResult(parseInfo, errorMessage, errorDescription);
1400                 return false;
1401             }
1402         }
1403     }
1404     if (!whereExpression().validate(&parseInfo)) {
1405         setResult(parseInfo, errorMessage, errorDescription);
1406         return false;
1407     }
1408     return true;
1409 }