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(¶ms);
1384 }
1385 }
1386 KDbExpression where = whereExpression();
1387 if (!where.isNull()) {
1388 where.getQueryParameters(¶ms);
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