1 /* This file is part of the KDE project
2    Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
3 
4    This library 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 library 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 library; see the file COPYING.LIB.  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 "KDbQuerySchema.h"
21 #include "KDbQuerySchema_p.h"
22 #include "KDbQueryAsterisk.h"
23 #include "KDbConnection.h"
24 #include "KDbConnection_p.h"
25 #include "kdb_debug.h"
26 #include "KDbLookupFieldSchema.h"
27 #include "KDbOrderByColumn.h"
28 #include "KDbParser_p.h"
29 #include "KDbQuerySchemaParameter.h"
30 #include "KDbRelationship.h"
31 
escapeIdentifier(const QString & name,KDbConnection * conn,KDb::IdentifierEscapingType escapingType)32 QString escapeIdentifier(const QString& name, KDbConnection *conn,
33                                 KDb::IdentifierEscapingType escapingType)
34 {
35     switch (escapingType) {
36     case KDb::DriverEscaping:
37         if (conn)
38             return conn->escapeIdentifier(name);
39         break;
40     case KDb::KDbEscaping:
41         return KDb::escapeIdentifier(name);
42     }
43     return QLatin1Char('"') + name + QLatin1Char('"');
44 }
45 
KDbQuerySchema()46 KDbQuerySchema::KDbQuerySchema()
47         : KDbFieldList(false)//fields are not owned by KDbQuerySchema object
48         , KDbObject(KDb::QueryObjectType)
49         , d(new KDbQuerySchemaPrivate(this))
50 {
51 }
52 
KDbQuerySchema(KDbTableSchema * tableSchema)53 KDbQuerySchema::KDbQuerySchema(KDbTableSchema *tableSchema)
54         : KDbFieldList(false)//fields are not owned by KDbQuerySchema object
55         , KDbObject(KDb::QueryObjectType)
56         , d(new KDbQuerySchemaPrivate(this))
57 {
58     if (tableSchema) {
59         d->masterTable = tableSchema;
60         /*if (!d->masterTable) {
61           kdbWarning() << "!d->masterTable";
62           m_name.clear();
63           return;
64         }*/
65         addTable(d->masterTable);
66         //defaults:
67         //inherit name from a table
68         setName(d->masterTable->name());
69         //inherit caption from a table
70         setCaption(d->masterTable->caption());
71 
72         // add explicit field list to avoid problems (e.g. with fields added outside of the app):
73         foreach(KDbField* f, *d->masterTable->fields()) {
74             addField(f);
75         }
76     }
77 }
78 
KDbQuerySchema(const KDbQuerySchema & querySchema,KDbConnection * conn)79 KDbQuerySchema::KDbQuerySchema(const KDbQuerySchema& querySchema, KDbConnection *conn)
80         : KDbFieldList(querySchema, false /* !deepCopyFields */)
81         , KDbObject(querySchema)
82         , d(new KDbQuerySchemaPrivate(this, querySchema.d))
83 {
84     //only deep copy query asterisks
85     foreach(KDbField* f, *querySchema.fields()) {
86         KDbField *copiedField;
87         if (dynamic_cast<KDbQueryAsterisk*>(f)) {
88             copiedField = f->copy();
89             if (static_cast<const KDbFieldList *>(f->parent()) == &querySchema) {
90                 copiedField->setParent(this);
91             }
92         }
93         else {
94             copiedField = f;
95         }
96         addField(copiedField);
97     }
98     // this deep copy must be after the 'd' initialization because fieldsExpanded() is used there
99     d->orderByColumnList = new KDbOrderByColumnList(*querySchema.d->orderByColumnList, conn,
100                                                     const_cast<KDbQuerySchema*>(&querySchema), this);
101 }
102 
~KDbQuerySchema()103 KDbQuerySchema::~KDbQuerySchema()
104 {
105     delete d;
106 }
107 
clear()108 void KDbQuerySchema::clear()
109 {
110     KDbFieldList::clear();
111     KDbObject::clear();
112     d->clear();
113 }
114 
115 /*virtual*/
insertField(int position,KDbField * field)116 bool KDbQuerySchema::insertField(int position, KDbField *field)
117 {
118     return insertFieldInternal(position, field, -1/*don't bind*/, true);
119 }
120 
insertInvisibleField(int position,KDbField * field)121 bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field)
122 {
123     return insertFieldInternal(position, field, -1/*don't bind*/, false);
124 }
125 
insertField(int position,KDbField * field,int bindToTable)126 bool KDbQuerySchema::insertField(int position, KDbField *field, int bindToTable)
127 {
128     return insertFieldInternal(position, field, bindToTable, true);
129 }
130 
insertInvisibleField(int position,KDbField * field,int bindToTable)131 bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field, int bindToTable)
132 {
133     return insertFieldInternal(position, field, bindToTable, false);
134 }
135 
insertFieldInternal(int position,KDbField * field,int bindToTable,bool visible)136 bool KDbQuerySchema::insertFieldInternal(int position, KDbField *field,
137                                          int bindToTable, bool visible)
138 {
139     if (!field) {
140         kdbWarning() << "!field";
141         return false;
142     }
143 
144     if (position > fieldCount()) {
145         kdbWarning() << "position" << position << "out of range";
146         return false;
147     }
148     if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) {
149         kdbWarning() << "field" << field->name() << "must contain table information!";
150         return false;
151     }
152     if (fieldCount() >= d->visibility.size()) {
153         d->visibility.resize(d->visibility.size()*2);
154         d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2);
155     }
156     d->clearCachedData();
157     if (!KDbFieldList::insertField(position, field)) {
158         return false;
159     }
160     if (field->isQueryAsterisk()) {
161         d->asterisks.append(field);
162         //if this is single-table asterisk,
163         //add a table to list if doesn't exist there:
164         if (field->table() && !d->tables.contains(field->table()))
165             d->tables.append(field->table());
166     } else if (field->table()) {
167         //add a table to list if doesn't exist there:
168         if (!d->tables.contains(field->table()))
169             d->tables.append(field->table());
170     }
171     //update visibility
172     //--move bits to make a place for a new one
173     for (int i = fieldCount() - 1; i > position; i--)
174         d->visibility.setBit(i, d->visibility.testBit(i - 1));
175     d->visibility.setBit(position, visible);
176 
177     //bind to table
178     if (bindToTable < -1 || bindToTable > d->tables.count()) {
179         kdbWarning() << "bindToTable" << bindToTable << "out of range";
180         bindToTable = -1;
181     }
182     //--move items to make a place for a new one
183     for (int i = fieldCount() - 1; i > position; i--)
184         d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1];
185     d->tablesBoundToColumns[position] = bindToTable;
186 
187 #ifdef KDB_QUERYSCHEMA_DEBUG
188     querySchemaDebug() << "bound to table" << bindToTable;
189     if (bindToTable == -1)
190         querySchemaDebug() << " <NOT SPECIFIED>";
191     else
192         querySchemaDebug() << " name=" << d->tables.at(bindToTable)->name()
193                            << " alias=" << tableAlias(bindToTable);
194     QString s;
195     for (int i = 0; i < fieldCount();i++)
196         s += (QString::number(d->tablesBoundToColumns[i]) + QLatin1Char(' '));
197     querySchemaDebug() << "tablesBoundToColumns == [" << s << "]";
198 #endif
199 
200     if (field->isExpression())
201         d->regenerateExprAliases = true;
202 
203     return true;
204 }
205 
tableBoundToColumn(int columnPosition) const206 int KDbQuerySchema::tableBoundToColumn(int columnPosition) const
207 {
208     int res = d->tablesBoundToColumns.value(columnPosition, -99);
209     if (res == -99) {
210         kdbWarning() << "columnPosition" << columnPosition << "out of range";
211         return -1;
212     }
213     return res;
214 }
215 
addField(KDbField * field)216 bool KDbQuerySchema::addField(KDbField* field)
217 {
218     return insertField(fieldCount(), field);
219 }
220 
addField(KDbField * field,int bindToTable)221 bool KDbQuerySchema::addField(KDbField* field, int bindToTable)
222 {
223     return insertField(fieldCount(), field, bindToTable);
224 }
225 
addInvisibleField(KDbField * field)226 bool KDbQuerySchema::addInvisibleField(KDbField* field)
227 {
228     return insertInvisibleField(fieldCount(), field);
229 }
230 
addInvisibleField(KDbField * field,int bindToTable)231 bool KDbQuerySchema::addInvisibleField(KDbField* field, int bindToTable)
232 {
233     return insertInvisibleField(fieldCount(), field, bindToTable);
234 }
235 
removeField(KDbField * field)236 bool KDbQuerySchema::removeField(KDbField *field)
237 {
238     int indexOfAsterisk = -1;
239     if (field->isQueryAsterisk()) {
240         indexOfAsterisk = d->asterisks.indexOf(field);
241     }
242     if (!KDbFieldList::removeField(field)) {
243         return false;
244     }
245     d->clearCachedData();
246     if (indexOfAsterisk >= 0) {
247         //querySchemaDebug() << "d->asterisks.removeAt:" << field;
248         //field->debug();
249         d->asterisks.removeAt(indexOfAsterisk); //this will destroy this asterisk
250     }
251 //! @todo should we also remove table for this field or asterisk?
252     return true;
253 }
254 
addExpressionInternal(const KDbExpression & expr,bool visible)255 bool KDbQuerySchema::addExpressionInternal(const KDbExpression& expr, bool visible)
256 {
257     KDbField *field = new KDbField(this, expr);
258     bool ok;
259     if (visible) {
260         ok = addField(field);
261     } else {
262         ok = addInvisibleField(field);
263     }
264     if (!ok) {
265         delete field;
266     }
267     d->ownedExpressionFields.append(field);
268     return ok;
269 }
270 
addExpression(const KDbExpression & expr)271 bool KDbQuerySchema::addExpression(const KDbExpression& expr)
272 {
273     return addExpressionInternal(expr, true);
274 }
275 
addInvisibleExpression(const KDbExpression & expr)276 bool KDbQuerySchema::addInvisibleExpression(const KDbExpression& expr)
277 {
278     return addExpressionInternal(expr, false);
279 }
280 
isColumnVisible(int position) const281 bool KDbQuerySchema::isColumnVisible(int position) const
282 {
283     return (position < fieldCount()) ? d->visibility.testBit(position) : false;
284 }
285 
setColumnVisible(int position,bool visible)286 void KDbQuerySchema::setColumnVisible(int position, bool visible)
287 {
288     if (position < fieldCount())
289         d->visibility.setBit(position, visible);
290 }
291 
addAsteriskInternal(KDbQueryAsterisk * asterisk,bool visible)292 bool KDbQuerySchema::addAsteriskInternal(KDbQueryAsterisk *asterisk, bool visible)
293 {
294     if (!asterisk) {
295         return false;
296     }
297     //make unique name
298     asterisk->setName((asterisk->table() ? (asterisk->table()->name() + QLatin1String(".*"))
299                                          : QString(QLatin1Char('*')))
300                        + QString::number(asterisks()->count()));
301     return visible ? addField(asterisk) : addInvisibleField(asterisk);
302 }
303 
addAsterisk(KDbQueryAsterisk * asterisk)304 bool KDbQuerySchema::addAsterisk(KDbQueryAsterisk *asterisk)
305 {
306     return addAsteriskInternal(asterisk, true);
307 }
308 
addInvisibleAsterisk(KDbQueryAsterisk * asterisk)309 bool KDbQuerySchema::addInvisibleAsterisk(KDbQueryAsterisk *asterisk)
310 {
311     return addAsteriskInternal(asterisk, false);
312 }
313 
operator <<(QDebug dbg,const KDbConnectionAndQuerySchema & connectionAndSchema)314 QDebug operator<<(QDebug dbg, const KDbConnectionAndQuerySchema &connectionAndSchema)
315 {
316     KDbConnection* conn = std::get<0>(connectionAndSchema);
317     const KDbQuerySchema& query = std::get<1>(connectionAndSchema);
318     //fields
319     KDbTableSchema *mt = query.masterTable();
320     dbg.nospace() << "QUERY";
321     dbg.space() << static_cast<const KDbObject&>(query) << '\n';
322     dbg.nospace() << " - MASTERTABLE=" << (mt ? mt->name() : QLatin1String("<NULL>"))
323         << "\n - COLUMNS:\n";
324     if (query.fieldCount() > 0)
325         dbg.nospace() << static_cast<const KDbFieldList&>(query) << '\n';
326     else
327         dbg.nospace() << "<NONE>\n";
328 
329     if (query.fieldCount() == 0)
330         dbg.nospace() << " - NO FIELDS\n";
331     else
332         dbg.nospace() << " - FIELDS EXPANDED (";
333 
334     int fieldsExpandedCount = 0;
335     bool first;
336     if (query.fieldCount() > 0) {
337         const KDbQueryColumnInfo::Vector fe(query.fieldsExpanded(conn));
338         fieldsExpandedCount = fe.size();
339         dbg.nospace() << fieldsExpandedCount << "):\n";
340         first = true;
341         for (int i = 0; i < fieldsExpandedCount; i++) {
342             KDbQueryColumnInfo *ci = fe[i];
343             if (first)
344                 first = false;
345             else
346                 dbg.nospace() << ",\n";
347             dbg.nospace() << *ci;
348         }
349         dbg.nospace() << '\n';
350     }
351 
352     //it's safer to delete fieldsExpanded for now
353     // (debugString() could be called before all fields are added)
354 
355     //bindings
356     dbg.nospace() << " - BINDINGS:\n";
357     bool bindingsExist = false;
358     for (int i = 0; i < query.fieldCount(); i++) {
359         const int tablePos = query.tableBoundToColumn(i);
360         if (tablePos >= 0) {
361             const QString tAlias(query.tableAlias(tablePos));
362             if (!tAlias.isEmpty()) {
363                 bindingsExist = true;
364                 dbg.space() << "FIELD";
365                 dbg.space() << static_cast<const KDbFieldList&>(query).field(i)->name();
366                 dbg.space() << "USES ALIAS";
367                 dbg.space() << tAlias;
368                 dbg.space() << "OF TABLE";
369                 dbg.space() << query.tables()->at(tablePos)->name() << '\n';
370             }
371         }
372     }
373     if (!bindingsExist) {
374         dbg.nospace() << "<NONE>\n";
375     }
376 
377     //tables
378     dbg.nospace() << " - TABLES:\n";
379     first = true;
380     foreach(KDbTableSchema *table, *query.tables()) {
381         if (first)
382             first = false;
383         else
384             dbg.nospace() << ",";
385         dbg.space() << table->name();
386     }
387     if (query.tables()->isEmpty())
388         dbg.nospace() << "<NONE>";
389 
390     //aliases
391     dbg.nospace() << "\n - COLUMN ALIASES:\n";
392     if (query.columnAliasesCount() == 0) {
393         dbg.nospace() << "<NONE>\n";
394     }
395     else {
396         int i = -1;
397         foreach(KDbField *f, *query.fields()) {
398             i++;
399             const QString alias(query.columnAlias(i));
400             if (!alias.isEmpty()) {
401                 dbg.nospace() << QString::fromLatin1("FIELD #%1:").arg(i);
402                 dbg.space() << (f->name().isEmpty()
403                     ? QLatin1String("<NONAME>") : f->name()) << " -> " << alias << '\n';
404             }
405         }
406     }
407 
408     dbg.nospace() << "- TABLE ALIASES:\n";
409     if (query.tableAliasesCount() == 0) {
410         dbg.nospace() << "<NONE>\n";
411     }
412     else {
413         int i = -1;
414         foreach(KDbTableSchema* table, *query.tables()) {
415             i++;
416             const QString alias(query.tableAlias(i));
417             if (!alias.isEmpty()) {
418                 dbg.nospace() << QString::fromLatin1("table #%1:").arg(i);
419                 dbg.space() << (table->name().isEmpty()
420                     ? QLatin1String("<NONAME>") : table->name()) << " -> " << alias << '\n';
421             }
422         }
423     }
424     if (!query.whereExpression().isNull()) {
425         dbg.nospace() << " - WHERE EXPRESSION:\n" << query.whereExpression() << '\n';
426     }
427     dbg.nospace() << qPrintable(QString::fromLatin1(" - ORDER BY (%1):\n").arg(query.orderByColumnList()->count()));
428     if (query.orderByColumnList()->isEmpty()) {
429         dbg.nospace() << "<NONE>\n";
430     } else {
431         dbg.nospace() << *query.orderByColumnList();
432     }
433     return dbg.nospace();
434 }
435 
masterTable() const436 KDbTableSchema* KDbQuerySchema::masterTable() const
437 {
438     if (d->masterTable)
439         return d->masterTable;
440     if (d->tables.isEmpty())
441         return nullptr;
442 
443     //try to find master table if there's only one table (with possible aliasses)
444     QString tableNameLower;
445     int num = -1;
446     foreach(KDbTableSchema *table, d->tables) {
447         num++;
448         if (!tableNameLower.isEmpty() && table->name().toLower() != tableNameLower) {
449             //two or more different tables
450             return nullptr;
451         }
452         tableNameLower = tableAlias(num);
453     }
454     return d->tables.first();
455 }
456 
setMasterTable(KDbTableSchema * table)457 void KDbQuerySchema::setMasterTable(KDbTableSchema *table)
458 {
459     if (table)
460         d->masterTable = table;
461 }
462 
tables() const463 QList<KDbTableSchema*>* KDbQuerySchema::tables() const
464 {
465     return &d->tables;
466 }
467 
addTable(KDbTableSchema * table,const QString & alias)468 void KDbQuerySchema::addTable(KDbTableSchema *table, const QString& alias)
469 {
470     querySchemaDebug() << (void *)table << "alias=" << alias;
471     if (!table)
472         return;
473 
474     // only append table if: it has alias or it has no alias but there is no such table on the list
475     if (alias.isEmpty() && d->tables.contains(table)) {
476         int num = -1;
477         foreach(KDbTableSchema *t, d->tables) {
478             num++;
479             if (0 == t->name().compare(table->name(), Qt::CaseInsensitive)) {
480                 if (tableAlias(num).isEmpty()) {
481                     querySchemaDebug() << "table" << table->name() << "without alias already added";
482                     return;
483                 }
484             }
485         }
486     }
487     d->tables.append(table);
488     if (!alias.isEmpty())
489         setTableAlias(d->tables.count() - 1, alias);
490 }
491 
removeTable(KDbTableSchema * table)492 void KDbQuerySchema::removeTable(KDbTableSchema *table)
493 {
494     if (!table)
495         return;
496     if (d->masterTable == table)
497         d->masterTable = nullptr;
498     d->tables.removeAt(d->tables.indexOf(table));
499 //! @todo remove fields!
500 }
501 
table(const QString & tableName) const502 KDbTableSchema* KDbQuerySchema::table(const QString& tableName) const
503 {
504 //! @todo maybe use tables_byname?
505     foreach(KDbTableSchema *table, d->tables) {
506         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
507             return table;
508         }
509     }
510     return nullptr;
511 }
512 
contains(KDbTableSchema * table) const513 bool KDbQuerySchema::contains(KDbTableSchema *table) const
514 {
515     return d->tables.contains(table);
516 }
517 
findTableField(const QString & fieldOrTableAndFieldName) const518 KDbField* KDbQuerySchema::findTableField(const QString &fieldOrTableAndFieldName) const
519 {
520     QString tableName, fieldName;
521     if (!KDb::splitToTableAndFieldParts(fieldOrTableAndFieldName,
522                                         &tableName, &fieldName,
523                                         KDb::SetFieldNameIfNoTableName)) {
524         return nullptr;
525     }
526     if (tableName.isEmpty()) {
527         foreach(KDbTableSchema *table, d->tables) {
528             if (table->field(fieldName))
529                 return table->field(fieldName);
530         }
531         return nullptr;
532     }
533     KDbTableSchema *tableSchema = table(tableName);
534     if (!tableSchema)
535         return nullptr;
536     return tableSchema->field(fieldName);
537 }
538 
columnAliasesCount() const539 int KDbQuerySchema::columnAliasesCount() const
540 {
541     return d->columnAliasesCount();
542 }
543 
columnAlias(int position) const544 QString KDbQuerySchema::columnAlias(int position) const
545 {
546     return d->columnAlias(position);
547 }
548 
hasColumnAlias(int position) const549 bool KDbQuerySchema::hasColumnAlias(int position) const
550 {
551     return d->hasColumnAlias(position);
552 }
553 
setColumnAlias(int position,const QString & alias)554 bool KDbQuerySchema::setColumnAlias(int position, const QString& alias)
555 {
556     if (position >= fieldCount()) {
557         kdbWarning() << "position"  << position << "out of range!";
558         return false;
559     }
560     const QString fixedAlias(alias.trimmed());
561     KDbField *f = KDbFieldList::field(position);
562     if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) {
563         kdbWarning() << "position" << position << "could not remove alias when no name is specified for expression column!";
564         return false;
565     }
566     return d->setColumnAlias(position, fixedAlias);
567 }
568 
tableAliasesCount() const569 int KDbQuerySchema::tableAliasesCount() const
570 {
571     return d->tableAliases.count();
572 }
573 
tableAlias(int position) const574 QString KDbQuerySchema::tableAlias(int position) const
575 {
576     return d->tableAliases.value(position);
577 }
578 
tableAlias(const QString & tableName) const579 QString KDbQuerySchema::tableAlias(const QString& tableName) const
580 {
581     const int pos = tablePosition(tableName);
582     if (pos == -1) {
583         return QString();
584     }
585     return d->tableAliases.value(pos);
586 }
587 
tableAliasOrName(const QString & tableName) const588 QString KDbQuerySchema::tableAliasOrName(const QString& tableName) const
589 {
590     const int pos = tablePosition(tableName);
591     if (pos == -1) {
592         return QString();
593     }
594     return KDb::iifNotEmpty(d->tableAliases.value(pos), tableName);
595 }
596 
tablePositionForAlias(const QString & name) const597 int KDbQuerySchema::tablePositionForAlias(const QString& name) const
598 {
599     return d->tablePositionForAlias(name);
600 }
601 
tablePosition(const QString & tableName) const602 int KDbQuerySchema::tablePosition(const QString& tableName) const
603 {
604     int num = -1;
605     foreach(KDbTableSchema* table, d->tables) {
606         num++;
607         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
608             return num;
609         }
610     }
611     return -1;
612 }
613 
tablePositions(const QString & tableName) const614 QList<int> KDbQuerySchema::tablePositions(const QString& tableName) const
615 {
616     QList<int> result;
617     int num = -1;
618     foreach(KDbTableSchema* table, d->tables) {
619         num++;
620         if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) {
621             result += num;
622         }
623     }
624     return result;
625 }
626 
hasTableAlias(int position) const627 bool KDbQuerySchema::hasTableAlias(int position) const
628 {
629     return d->tableAliases.contains(position);
630 }
631 
hasTableAlias(const QString & name) const632 bool KDbQuerySchema::hasTableAlias(const QString &name) const
633 {
634     return d->tablePositionForAlias(name) != -1;
635 }
636 
columnPositionForAlias(const QString & name) const637 int KDbQuerySchema::columnPositionForAlias(const QString& name) const
638 {
639     return d->columnPositionForAlias(name);
640 }
641 
hasColumnAlias(const QString & name) const642 bool KDbQuerySchema::hasColumnAlias(const QString &name) const
643 {
644     return d->columnPositionForAlias(name) != -1;
645 }
646 
setTableAlias(int position,const QString & alias)647 bool KDbQuerySchema::setTableAlias(int position, const QString& alias)
648 {
649     if (position >= d->tables.count()) {
650         kdbWarning() << "position"  << position << "out of range!";
651         return false;
652     }
653     const QString fixedAlias(alias.trimmed());
654     if (fixedAlias.isEmpty()) {
655         const QString oldAlias(d->tableAliases.take(position));
656         if (!oldAlias.isEmpty()) {
657             d->removeTablePositionForAlias(oldAlias);
658         }
659         return true;
660     }
661     return d->setTableAlias(position, fixedAlias);
662 }
663 
relationships() const664 QList<KDbRelationship*>* KDbQuerySchema::relationships() const
665 {
666     return &d->relations;
667 }
668 
asterisks() const669 KDbField::List* KDbQuerySchema::asterisks() const
670 {
671     return &d->asterisks;
672 }
673 
statement() const674 KDbEscapedString KDbQuerySchema::statement() const
675 {
676     return d->sql;
677 }
678 
setStatement(const KDbEscapedString & sql)679 void KDbQuerySchema::setStatement(const KDbEscapedString& sql)
680 {
681     d->sql = sql;
682 }
683 
field(KDbConnection * conn,const QString & identifier,ExpandMode mode) const684 const KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier,
685                                       ExpandMode mode) const
686 {
687     KDbQueryColumnInfo *ci = columnInfo(conn, identifier, mode);
688     return ci ? ci->field() : nullptr;
689 }
690 
field(KDbConnection * conn,const QString & identifier,ExpandMode mode)691 KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier, ExpandMode mode)
692 {
693     return const_cast<KDbField *>(
694         static_cast<const KDbQuerySchema *>(this)->field(conn, identifier, mode));
695 }
696 
field(int id)697 KDbField* KDbQuerySchema::field(int id)
698 {
699     return KDbFieldList::field(id);
700 }
701 
field(int id) const702 const KDbField* KDbQuerySchema::field(int id) const
703 {
704     return KDbFieldList::field(id);
705 }
706 
columnInfo(KDbConnection * conn,const QString & identifier,ExpandMode mode) const707 KDbQueryColumnInfo *KDbQuerySchema::columnInfo(KDbConnection *conn, const QString &identifier,
708                                                ExpandMode mode) const
709 {
710     const KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
711     return mode == ExpandMode::Expanded ? cache->columnInfosByNameExpanded.value(identifier)
712                                         : cache->columnInfosByName.value(identifier);
713 }
714 
fieldsExpandedInternal(KDbConnection * conn,FieldsExpandedMode mode,bool onlyVisible) const715 KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpandedInternal(
716         KDbConnection *conn, FieldsExpandedMode mode, bool onlyVisible) const
717 {
718     if (!conn) {
719         kdbWarning() << "Connection required";
720         return KDbQueryColumnInfo::Vector();
721     }
722     KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
723     const KDbQueryColumnInfo::Vector *realFieldsExpanded
724         = onlyVisible ? &cache->visibleFieldsExpanded : &cache->fieldsExpanded;
725     if (mode == FieldsExpandedMode::WithInternalFields
726         || mode == FieldsExpandedMode::WithInternalFieldsAndRecordId)
727     {
728         //a ref to a proper pointer (as we cache the vector for two cases)
729         KDbQueryColumnInfo::Vector& tmpFieldsExpandedWithInternal =
730             (mode == FieldsExpandedMode::WithInternalFields) ?
731                 (onlyVisible ? cache->visibleFieldsExpandedWithInternal : cache->fieldsExpandedWithInternal)
732               : (onlyVisible ? cache->visibleFieldsExpandedWithInternalAndRecordId : cache->fieldsExpandedWithInternalAndRecordId);
733         //special case
734         if (tmpFieldsExpandedWithInternal.isEmpty()) {
735             //glue expanded and internal fields and cache it
736             const int internalFieldCount = cache->internalFields.size();
737             const int fieldsExpandedVectorSize = realFieldsExpanded->size();
738             const int size = fieldsExpandedVectorSize + internalFieldCount
739                 + ((mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) ? 1 : 0) /*ROWID*/;
740             tmpFieldsExpandedWithInternal.resize(size);
741             for (int i = 0; i < fieldsExpandedVectorSize; ++i) {
742                 tmpFieldsExpandedWithInternal[i] = realFieldsExpanded->at(i);
743             }
744             if (internalFieldCount > 0) {
745                 for (int i = 0; i < internalFieldCount; ++i) {
746                     KDbQueryColumnInfo *info = cache->internalFields[i];
747                     tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + i] = info;
748                 }
749             }
750             if (mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) {
751                 if (!d->fakeRecordIdField) {
752                     d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), KDbField::BigInteger);
753                     d->fakeRecordIdCol = new KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true);
754                     d->fakeRecordIdCol->d->querySchema = this;
755                     d->fakeRecordIdCol->d->connection = conn;
756                 }
757                 tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + internalFieldCount] = d->fakeRecordIdCol;
758             }
759         }
760         return tmpFieldsExpandedWithInternal;
761     }
762 
763     if (mode == FieldsExpandedMode::Default) {
764         return *realFieldsExpanded;
765     }
766 
767     //mode == Unique:
768     QSet<QString> columnsAlreadyFound;
769     const int fieldsExpandedCount(realFieldsExpanded->count());
770     KDbQueryColumnInfo::Vector result(fieldsExpandedCount);   //initial size is set
771     //compute unique list
772     int uniqueListCount = 0;
773     for (int i = 0; i < fieldsExpandedCount; i++) {
774         KDbQueryColumnInfo *ci = realFieldsExpanded->at(i);
775         if (!columnsAlreadyFound.contains(ci->aliasOrName())) {
776             columnsAlreadyFound.insert(ci->aliasOrName());
777             result[uniqueListCount++] = ci;
778         }
779     }
780     result.resize(uniqueListCount); //update result size
781     return result;
782 }
783 
internalFields(KDbConnection * conn) const784 KDbQueryColumnInfo::Vector KDbQuerySchema::internalFields(KDbConnection *conn) const
785 {
786     KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn);
787     return cache->internalFields;
788 }
789 
expandedOrInternalField(KDbConnection * conn,int index) const790 KDbQueryColumnInfo* KDbQuerySchema::expandedOrInternalField(KDbConnection *conn, int index) const
791 {
792     return fieldsExpanded(conn, FieldsExpandedMode::WithInternalFields).value(index);
793 }
794 
lookupColumnKey(KDbField * foreignField,KDbField * field)795 inline static QString lookupColumnKey(KDbField *foreignField, KDbField* field)
796 {
797     QString res;
798     if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns
799         res = field->table()->name() + QLatin1Char('.');
800     return res + field->name() + QLatin1Char('_') + foreignField->table()->name()
801                + QLatin1Char('.') + foreignField->name();
802 }
803 
computeFieldsExpanded(KDbConnection * conn) const804 KDbQuerySchemaFieldsExpanded *KDbQuerySchema::computeFieldsExpanded(KDbConnection *conn) const
805 {
806     KDbQuerySchemaFieldsExpanded *cache = conn->d->fieldsExpanded(this);
807     if (cache) {
808         return cache;
809     }
810     cache = new KDbQuerySchemaFieldsExpanded;
811     QScopedPointer<KDbQuerySchemaFieldsExpanded> guard(cache);
812 
813     //collect all fields in a list (not a vector yet, because we do not know its size)
814     KDbQueryColumnInfo::List list; //temporary
815     KDbQueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields
816     QHash<KDbQueryColumnInfo*, bool> columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName
817     int i = 0;
818     int numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field
819     int fieldPosition = -1;
820     for (KDbField *f : *fields()) {
821         fieldPosition++;
822         if (f->isQueryAsterisk()) {
823             if (static_cast<KDbQueryAsterisk*>(f)->isSingleTableAsterisk()) {
824                 const KDbField::List *ast_fields = static_cast<KDbQueryAsterisk*>(f)->table()->fields();
825                 foreach(KDbField *ast_f, *ast_fields) {
826                     KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, QString()/*no field for asterisk!*/,
827                             isColumnVisible(fieldPosition));
828                     ci->d->querySchema = this;
829                     ci->d->connection = conn;
830                     list.append(ci);
831                     querySchemaDebug() << "caching (unexpanded) columns order:" << *ci
832                                        << "at position" << fieldPosition;
833                     cache->columnsOrder.insert(ci, fieldPosition);
834                 }
835             } else {//all-tables asterisk: iterate through table list
836                 foreach(KDbTableSchema *table, d->tables) {
837                     //add all fields from this table
838                     const KDbField::List *tab_fields = table->fields();
839                     foreach(KDbField *tab_f, *tab_fields) {
840 //! @todo (js): perhaps not all fields should be appended here
841 //      d->detailedVisibility += isFieldVisible(fieldPosition);
842 //      list.append(tab_f);
843                         KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, QString()/*no field for asterisk!*/,
844                                 isColumnVisible(fieldPosition));
845                         ci->d->querySchema = this;
846                         ci->d->connection = conn;
847                         list.append(ci);
848                         querySchemaDebug() << "caching (unexpanded) columns order:" << *ci
849                                            << "at position" << fieldPosition;
850                         cache->columnsOrder.insert(ci, fieldPosition);
851                     }
852                 }
853             }
854         } else {
855             //a single field
856             KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition));
857             ci->d->querySchema = this;
858             ci->d->connection = conn;
859             list.append(ci);
860             columnInfosOutsideAsterisks.insert(ci, true);
861             querySchemaDebug() << "caching (unexpanded) column's order:" << *ci << "at position"
862                                << fieldPosition;
863             cache->columnsOrder.insert(ci, fieldPosition);
864             cache->columnsOrderWithoutAsterisks.insert(ci, fieldPosition);
865 
866             //handle lookup field schema
867             KDbLookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema(*f) : nullptr;
868             if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0)
869                 continue;
870             // Lookup field schema found:
871             // Now we also need to fetch "visible" value from the lookup table, not only the value of binding.
872             // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken)
873             // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField"
874             KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource();
875             if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) {
876                 KDbTableSchema *lookupTable = conn->tableSchema(recordSource.name());
877                 KDbFieldList* visibleColumns = nullptr;
878                 KDbField *boundField = nullptr;
879                 if (lookupTable
880                         && lookupFieldSchema->boundColumn() < lookupTable->fieldCount()
881                         && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))
882                         && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) {
883                     KDbField *visibleColumn = nullptr;
884                     // for single visible column, just add it as-is
885                     if (visibleColumns->fieldCount() == 1) {
886                         visibleColumn = visibleColumns->fields()->first();
887                     } else {
888                         // for multiple visible columns, build an expression column
889                         // (the expression object will be owned by column info)
890                         visibleColumn = new KDbField();
891                         visibleColumn->setName(
892                             QString::fromLatin1("[multiple_visible_fields_%1]")
893                             .arg(++numberOfColumnsWithMultipleVisibleFields));
894                         visibleColumn->setExpression(
895                             KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
896                         cache->ownedVisibleFields.append(visibleColumn);   // remember to delete later
897                     }
898 
899                     KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo(
900                         visibleColumn, QString(), true /*visible*/, ci /*foreign*/);
901                     lookupCi->d->querySchema = this;
902                     lookupCi->d->connection = conn;
903                     lookup_list.append(lookupCi);
904                     /*
905                               //add visibleField to the list of SELECTed fields if it is not yes present there
906                               if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
907                                 if (!table( visibleField->table()->name() )) {
908                                 }
909                                 if (!sql.isEmpty())
910                                   sql += QString::fromLatin1(", ");
911                                 sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
912                                   + escapeIdentifier(visibleField->name(), drvEscaping));
913                               }*/
914                 }
915                 delete visibleColumns;
916             } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) {
917                 KDbQuerySchema *lookupQuery = conn->querySchema(recordSource.name());
918                 if (!lookupQuery)
919                     continue;
920                 const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(
921                     lookupQuery->fieldsExpanded(conn));
922                 if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count())
923                     continue;
924                 KDbQueryColumnInfo *boundColumnInfo = nullptr;
925                 if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn())))
926                     continue;
927                 KDbField *boundField = boundColumnInfo->field();
928                 if (!boundField)
929                     continue;
930                 const QList<int> visibleColumns(lookupFieldSchema->visibleColumns());
931                 bool ok = true;
932                 // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1]
933                 foreach(int visibleColumn, visibleColumns) {
934                     if (visibleColumn >= lookupQueryFieldsExpanded.count()) {
935                         ok = false;
936                         break;
937                     }
938                 }
939                 if (!ok)
940                     continue;
941                 KDbField *visibleColumn = nullptr;
942                 // for single visible column, just add it as-is
943                 if (visibleColumns.count() == 1) {
944                     visibleColumn = lookupQueryFieldsExpanded.value(visibleColumns.first())->field();
945                 } else {
946                     // for multiple visible columns, build an expression column
947                     // (the expression object will be owned by column info)
948                     visibleColumn = new KDbField();
949                     visibleColumn->setName(
950                         QString::fromLatin1("[multiple_visible_fields_%1]")
951                         .arg(++numberOfColumnsWithMultipleVisibleFields));
952                     visibleColumn->setExpression(
953                         KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/));
954                     cache->ownedVisibleFields.append(visibleColumn);   // remember to delete later
955                 }
956 
957                 KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo(
958                     visibleColumn, QString(), true /*visible*/, ci /*foreign*/);
959                 lookupCi->d->querySchema = this;
960                 lookupCi->d->connection = conn;
961                 lookup_list.append(lookupCi);
962                 /*
963                         //add visibleField to the list of SELECTed fields if it is not yes present there
964                         if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) {
965                           if (!table( visibleField->table()->name() )) {
966                           }
967                           if (!sql.isEmpty())
968                             sql += QString::fromLatin1(", ");
969                           sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "."
970                             + escapeIdentifier(visibleField->name(), drvEscaping));
971                         }*/
972             }
973         }
974     }
975     //prepare clean vector for expanded list, and a map for order information
976     cache->fieldsExpanded.resize(list.count());
977     cache->visibleFieldsExpanded.resize(list.count());
978 
979     /*fill (based on prepared 'list' and 'lookup_list'):
980      -the vector
981      -the map
982      -"fields by name" dictionary
983     */
984     i = -1;
985     int visibleIndex = -1;
986     foreach(KDbQueryColumnInfo* ci, list) {
987         i++;
988         cache->fieldsExpanded[i] = ci;
989         if (ci->isVisible()) {
990             ++visibleIndex;
991             cache->visibleFieldsExpanded[visibleIndex] = ci;
992         }
993         cache->columnsOrderExpanded.insert(ci, i);
994         //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded
995         if (!ci->alias().isEmpty()) {
996             //store alias and table.alias
997             if (!cache->columnInfosByNameExpanded.contains(ci->alias())) {
998                 cache->columnInfosByNameExpanded.insert(ci->alias(), ci);
999             }
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 
columnsOrder(KDbConnection * conn,ColumnsOrderMode mode) const1146 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 
pkeyFieldsOrder(KDbConnection * conn) const1158 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 
pkeyFieldCount(KDbConnection * conn)1192 int KDbQuerySchema::pkeyFieldCount(KDbConnection *conn)
1193 {
1194     (void)pkeyFieldsOrder(conn); /* rebuild information */
1195     return d->pkeyFieldCount;
1196 }
1197 
addRelationship(KDbField * field1,KDbField * field2)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 
autoIncrementFields(KDbConnection * conn) const1211 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
sqlColumnsList(const KDbQueryColumnInfo::List & infolist,KDbConnection * conn,KDb::IdentifierEscapingType escapingType)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 
autoIncrementSqlFieldsList(KDbConnection * conn) const1251 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 
setResult(const KDbParseInfoInternal & parseInfo,QString * errorMessage,QString * errorDescription)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 
setWhereExpression(const KDbExpression & expr,QString * errorMessage,QString * errorDescription)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 
addToWhereExpression(KDbField * field,const QVariant & value,KDbToken relation,QString * errorMessage,QString * errorDescription)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 
whereExpression() const1354 KDbExpression KDbQuerySchema::whereExpression() const
1355 {
1356     return d->whereExpr;
1357 }
1358 
setOrderByColumnList(const KDbOrderByColumnList & list)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 
orderByColumnList()1366 KDbOrderByColumnList* KDbQuerySchema::orderByColumnList()
1367 {
1368     return d->orderByColumnList;
1369 }
1370 
orderByColumnList() const1371 const KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() const
1372 {
1373     return d->orderByColumnList;
1374 }
1375 
parameters(KDbConnection * conn) const1376 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 
validate(QString * errorMessage,QString * errorDescription)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 }
1410