diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-01-20 01:29:50 +0000 |
commit | 8362bf63dea22bbf6736609b0f49c152f975eb63 (patch) | |
tree | 0eea3928e39e50fae91d4e68b21b1e6cbae25604 /kexi/kexidb/queryschema.cpp | |
download | koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.tar.gz koffice-8362bf63dea22bbf6736609b0f49c152f975eb63.zip |
Added old abandoned KDE3 version of koffice
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/koffice@1077364 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kexi/kexidb/queryschema.cpp')
-rw-r--r-- | kexi/kexidb/queryschema.cpp | 1859 |
1 files changed, 1859 insertions, 0 deletions
diff --git a/kexi/kexidb/queryschema.cpp b/kexi/kexidb/queryschema.cpp new file mode 100644 index 00000000..4da4d2b8 --- /dev/null +++ b/kexi/kexidb/queryschema.cpp @@ -0,0 +1,1859 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2007 Jaroslaw Staniek <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidb/queryschema.h" +#include "kexidb/driver.h" +#include "kexidb/connection.h" +#include "kexidb/expression.h" +#include "kexidb/parser/sqlparser.h" +#include "utils.h" +#include "lookupfieldschema.h" + +#include <assert.h> + +#include <qvaluelist.h> +#include <qasciidict.h> +#include <qptrdict.h> +#include <qintdict.h> +#include <qbitarray.h> + +#include <kdebug.h> +#include <klocale.h> + +using namespace KexiDB; + +QueryColumnInfo::QueryColumnInfo(Field *f, const QCString& _alias, bool _visible, + QueryColumnInfo *foreignColumn) + : field(f), alias(_alias), visible(_visible), m_indexForVisibleLookupValue(-1) + , m_foreignColumn(foreignColumn) +{ +} + +QueryColumnInfo::~QueryColumnInfo() +{ +} + +QString QueryColumnInfo::debugString() const +{ + return field->name() + + ( alias.isEmpty() ? QString::null + : (QString::fromLatin1(" AS ") + QString(alias)) ); +} + +//======================================= +namespace KexiDB { +//! @internal +class QuerySchemaPrivate +{ + public: + QuerySchemaPrivate(QuerySchema* q, QuerySchemaPrivate* copy = 0) + : query(q) + , masterTable(0) + , fakeRowIDField(0) + , fakeRowIDCol(0) + , maxIndexWithAlias(-1) + , visibility(64) + , fieldsExpanded(0) + , internalFields(0) + , fieldsExpandedWithInternalAndRowID(0) + , fieldsExpandedWithInternal(0) + , autoincFields(0) + , columnsOrder(0) + , columnsOrderWithoutAsterisks(0) + , columnsOrderExpanded(0) + , pkeyFieldsOrder(0) + , pkeyFieldsCount(0) + , tablesBoundToColumns(64, -1) + , tablePositionsForAliases(67, false) + , columnPositionsForAliases(67, false) + , whereExpr(0) + , ownedVisibleColumns(0) + , regenerateExprAliases(false) + { + columnAliases.setAutoDelete(true); + tableAliases.setAutoDelete(true); + asterisks.setAutoDelete(true); + relations.setAutoDelete(true); + tablePositionsForAliases.setAutoDelete(true); + columnPositionsForAliases.setAutoDelete(true); + visibility.fill(false); + if (copy) { + // deep copy + *this = *copy; + if (copy->fieldsExpanded) + fieldsExpanded = new QueryColumnInfo::Vector(*copy->fieldsExpanded); + if (copy->internalFields) + internalFields = new QueryColumnInfo::Vector(*copy->internalFields); + if (copy->fieldsExpandedWithInternalAndRowID) + fieldsExpandedWithInternalAndRowID = new QueryColumnInfo::Vector( + *copy->fieldsExpandedWithInternalAndRowID); + if (copy->fieldsExpandedWithInternal) + fieldsExpandedWithInternal = new QueryColumnInfo::Vector( + *copy->fieldsExpandedWithInternal); + if (copy->autoincFields) + autoincFields = new QueryColumnInfo::List(*copy->autoincFields); + if (copy->columnsOrder) + columnsOrder = new QMap<QueryColumnInfo*,int>(*copy->columnsOrder); + if (copy->columnsOrderWithoutAsterisks) + columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>( + *copy->columnsOrderWithoutAsterisks); + if (copy->columnsOrderExpanded) + columnsOrderExpanded = new QMap<QueryColumnInfo*,int>(*copy->columnsOrderExpanded); + if (copy->pkeyFieldsOrder) + pkeyFieldsOrder = new QValueVector<int>(*copy->pkeyFieldsOrder); + if (copy->whereExpr) + whereExpr = copy->whereExpr->copy(); + if (copy->fakeRowIDCol) + fakeRowIDCol = new QueryColumnInfo(*copy->fakeRowIDCol); + if (copy->fakeRowIDField) + fakeRowIDField = new Field(*copy->fakeRowIDField); + if (copy->ownedVisibleColumns) + ownedVisibleColumns = new Field::List(*copy->ownedVisibleColumns); + } + } + ~QuerySchemaPrivate() + { + delete fieldsExpanded; + delete internalFields; + delete fieldsExpandedWithInternalAndRowID; + delete fieldsExpandedWithInternal; + delete autoincFields; + delete columnsOrder; + delete columnsOrderWithoutAsterisks; + delete columnsOrderExpanded; + delete pkeyFieldsOrder; + delete whereExpr; + delete fakeRowIDCol; + delete fakeRowIDField; + delete ownedVisibleColumns; + } + + void clear() + { + columnAliases.clear(); + tableAliases.clear(); + asterisks.clear(); + relations.clear(); + masterTable = 0; + tables.clear(); + clearCachedData(); + delete pkeyFieldsOrder; + pkeyFieldsOrder=0; + visibility.fill(false); + tablesBoundToColumns = QValueVector<int>(64,-1); + tablePositionsForAliases.clear(); + columnPositionsForAliases.clear(); + } + + void clearCachedData() + { + orderByColumnList.clear(); + if (fieldsExpanded) { + delete fieldsExpanded; + fieldsExpanded = 0; + delete internalFields; + internalFields = 0; + delete columnsOrder; + columnsOrder = 0; + delete columnsOrderWithoutAsterisks; + columnsOrderWithoutAsterisks = 0; + delete columnsOrderExpanded; + columnsOrderExpanded = 0; + delete autoincFields; + autoincFields = 0; + autoIncrementSQLFieldsList = QString::null; + columnInfosByNameExpanded.clear(); + columnInfosByName.clear(); + delete ownedVisibleColumns; + ownedVisibleColumns = 0; + } + } + + void setColumnAliasInternal(uint position, const QCString& alias) + { + columnAliases.replace(position, new QCString(alias)); + columnPositionsForAliases.replace(alias, new int(position)); + maxIndexWithAlias = QMAX( maxIndexWithAlias, (int)position ); + } + + void setColumnAlias(uint position, const QCString& alias) + { + QCString *oldAlias = columnAliases.take(position); + if (oldAlias) { + tablePositionsForAliases.remove(*oldAlias); + delete oldAlias; + } + if (alias.isEmpty()) { + maxIndexWithAlias = -1; + } + else { + setColumnAliasInternal(position, alias); + } + } + + bool hasColumnAliases() + { + tryRegenerateExprAliases(); + return !columnAliases.isEmpty(); + } + + QCString* columnAlias(uint position) + { + tryRegenerateExprAliases(); + return columnAliases[position]; + } + + QuerySchema *query; + + /*! Master table of the query. (may be NULL) + Any data modifications can be performed if we know master table. + If null, query's records cannot be modified. */ + TableSchema *masterTable; + + /*! List of tables used in this query */ + TableSchema::List tables; + + Field *fakeRowIDField; //! used to mark a place for ROWID + QueryColumnInfo *fakeRowIDCol; //! used to mark a place for ROWID + + protected: + void tryRegenerateExprAliases() + { + if (!regenerateExprAliases) + return; + //regenerate missing aliases for experessions + Field *f; + uint p=0; + uint colNum=0; //used to generate a name + QCString columnAlias; + for (Field::ListIterator it(query->fieldsIterator()); (f = it.current()); ++it, p++) { + if (f->isExpression() && !columnAliases[p]) { + //missing + for (;;) { //find 1st unused + colNum++; + columnAlias = (i18n("short for 'expression' word (only latin letters, please)", "expr") + + QString::number(colNum)).latin1(); + if (!tablePositionsForAliases[columnAlias]) + break; + } + setColumnAliasInternal(p, columnAlias); + } + } + regenerateExprAliases = false; + } + + /*! Used to mapping columns to its aliases for this query */ + QIntDict<QCString> columnAliases; + + public: + /*! Used to mapping tables to its aliases for this query */ + QIntDict<QCString> tableAliases; + + /*! Helper used with aliases */ + int maxIndexWithAlias; + + /*! Helper used with tableAliases */ + int maxIndexWithTableAlias; + + /*! Used to store visibility flag for every field */ + QBitArray visibility; + + /*! List of asterisks defined for this query */ + Field::List asterisks; + + /*! Temporary field vector for using in fieldsExpanded() */ +// Field::Vector *fieldsExpanded; + QueryColumnInfo::Vector *fieldsExpanded; + + /*! Temporary field vector containing internal fields used for lookup columns. */ + QueryColumnInfo::Vector *internalFields; + + /*! Temporary, used to cache sum of expanded fields and internal fields (+rowid) used for lookup columns. + Contains not auto-deleted items.*/ + QueryColumnInfo::Vector *fieldsExpandedWithInternalAndRowID; + + /*! Temporary, used to cache sum of expanded fields and internal fields used for lookup columns. + Contains not auto-deleted items.*/ + QueryColumnInfo::Vector *fieldsExpandedWithInternal; + + /*! A list of fields for ORDER BY section. @see QuerySchema::orderByColumnList(). */ + OrderByColumnList orderByColumnList; + + /*! A cache for autoIncrementFields(). */ + QueryColumnInfo::List *autoincFields; + + /*! A cache for autoIncrementSQLFieldsList(). */ + QString autoIncrementSQLFieldsList; + QGuardedPtr<Driver> lastUsedDriverForAutoIncrementSQLFieldsList; + + /*! A map for fast lookup of query columns' order (unexpanded version). */ + QMap<QueryColumnInfo*,int> *columnsOrder; + + /*! A map for fast lookup of query columns' order (unexpanded version without asterisks). */ + QMap<QueryColumnInfo*,int> *columnsOrderWithoutAsterisks; + + /*! A map for fast lookup of query columns' order. + This is exactly opposite information compared to vector returned + by fieldsExpanded() */ + QMap<QueryColumnInfo*,int> *columnsOrderExpanded; + +// QValueList<bool> detailedVisibility; + + /*! order of PKEY fields (e.g. for updateRow() ) */ + QValueVector<int> *pkeyFieldsOrder; + + /*! number of PKEY fields within the query */ + uint pkeyFieldsCount; + + /*! forced (predefined) statement */ + QString statement; + + /*! Relationships defined for this query. */ + Relationship::List relations; + + /*! Information about columns bound to tables. + Used a table is used in FROM section more than once + (using table aliases). + + This list is updated by insertField(uint position, Field *field, + int bindToTable, bool visible), using bindToTable parameter. + + Example: for this statement: + SELECT t1.a, othertable.x, t2.b FROM table t1, table t2, othertable; + tablesBoundToColumns list looks like this: + [ 0, -1, 1 ] + - first column is bound to table 0 "t1" + - second coulmn is not specially bound (othertable.x isn't ambiguous) + - third column is bound to table 1 "t2" + */ + QValueVector<int> tablesBoundToColumns; + + /*! Collects table positions for aliases: used in tablePositionForAlias(). */ + QAsciiDict<int> tablePositionsForAliases; + + /*! Collects column positions for aliases: used in columnPositionForAlias(). */ + QAsciiDict<int> columnPositionsForAliases; + + /*! WHERE expression */ + BaseExpr *whereExpr; + + QDict<QueryColumnInfo> columnInfosByNameExpanded; + + QDict<QueryColumnInfo> columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped + + //! field schemas created for multiple joined columns like a||' '||b||' '||c + Field::List *ownedVisibleColumns; + + /*! Set by insertField(): true, if aliases for expression columns should + be generated on next columnAlias() call. */ + bool regenerateExprAliases : 1; +}; +} + +//======================================= + +OrderByColumn::OrderByColumn() + : m_column(0) + , m_pos(-1) + , m_field(0) + , m_ascending(true) +{ +} + +OrderByColumn::OrderByColumn(QueryColumnInfo& column, bool ascending, int pos) + : m_column(&column) + , m_pos(pos) + , m_field(0) + , m_ascending(ascending) +{ +} + +OrderByColumn::OrderByColumn(Field& field, bool ascending) + : m_column(0) + , m_pos(-1) + , m_field(&field) + , m_ascending(ascending) +{ +} + +OrderByColumn::~OrderByColumn() +{ +} + +QString OrderByColumn::debugString() const +{ + QString orderString( m_ascending ? "ascending" : "descending" ); + if (m_column) { + if (m_pos>-1) + return QString("COLUMN_AT_POSITION_%1(%2, %3)") + .arg(m_pos+1).arg(m_column->debugString()).arg(orderString); + else + return QString("COLUMN(%1, %2)").arg(m_column->debugString()).arg(orderString); + } + return m_field ? QString("FIELD(%1, %2)").arg(m_field->debugString()).arg(orderString) + : QString("NONE"); +} + +QString OrderByColumn::toSQLString(bool includeTableName, Driver *drv, int identifierEscaping) const +{ + const QString orderString( m_ascending ? "" : " DESC" ); + QString fieldName, tableName; + if (m_column) { + if (m_pos>-1) + return QString::number(m_pos+1) + orderString; + else { + if (includeTableName && m_column->alias.isEmpty()) { + tableName = m_column->field->table()->name(); + if (drv) + tableName = drv->escapeIdentifier(tableName, identifierEscaping); + tableName += "."; + } + fieldName = m_column->aliasOrName(); + if (drv) + fieldName = drv->escapeIdentifier(fieldName, identifierEscaping); + } + } + else { + if (includeTableName) { + tableName = m_field->table()->name(); + if (drv) + tableName = drv->escapeIdentifier(tableName, identifierEscaping); + tableName += "."; + } + fieldName = m_field ? m_field->name() : "??"/*error*/; + if (drv) + fieldName = drv->escapeIdentifier(fieldName, identifierEscaping); + } + return tableName + fieldName + orderString; +} + +//======================================= + +OrderByColumnList::OrderByColumnList() + : OrderByColumnListBase() +{ +} + +bool OrderByColumnList::appendFields(QuerySchema& querySchema, + const QString& field1, bool ascending1, + const QString& field2, bool ascending2, + const QString& field3, bool ascending3, + const QString& field4, bool ascending4, + const QString& field5, bool ascending5) +{ + uint numAdded = 0; +#define ADD_COL(fieldName, ascending) \ + if (ok && !fieldName.isEmpty()) { \ + if (!appendField( querySchema, fieldName, ascending )) \ + ok = false; \ + else \ + numAdded++; \ + } + bool ok = true; + ADD_COL(field1, ascending1); + ADD_COL(field2, ascending2); + ADD_COL(field3, ascending3); + ADD_COL(field4, ascending4); + ADD_COL(field5, ascending5); +#undef ADD_COL + if (ok) + return true; + for (uint i=0; i<numAdded; i++) + pop_back(); + return false; +} + +OrderByColumnList::~OrderByColumnList() +{ +} + +void OrderByColumnList::appendColumn(QueryColumnInfo& columnInfo, bool ascending) +{ + appendColumn( OrderByColumn(columnInfo, ascending) ); +} + +bool OrderByColumnList::appendColumn(QuerySchema& querySchema, bool ascending, int pos) +{ + QueryColumnInfo::Vector fieldsExpanded( querySchema.fieldsExpanded() ); + QueryColumnInfo* ci = (pos >= (int)fieldsExpanded.size()) ? 0 : fieldsExpanded[pos]; + if (!ci) + return false; + appendColumn( OrderByColumn(*ci, ascending, pos) ); + return true; +} + +void OrderByColumnList::appendField(Field& field, bool ascending) +{ + appendColumn( OrderByColumn(field, ascending) ); +} + +bool OrderByColumnList::appendField(QuerySchema& querySchema, + const QString& fieldName, bool ascending) +{ + QueryColumnInfo *columnInfo = querySchema.columnInfo( fieldName ); + if (columnInfo) { + appendColumn( OrderByColumn(*columnInfo, ascending) ); + return true; + } + Field *field = querySchema.findTableField(fieldName); + if (field) { + appendColumn( OrderByColumn(*field, ascending) ); + return true; + } + KexiDBWarn << "OrderByColumnList::addColumn(QuerySchema& querySchema, " + "const QString& column, bool ascending): no such field \"" << fieldName << "\"" << endl; + return false; +} + +void OrderByColumnList::appendColumn(const OrderByColumn& column) +{ + append( column ); +} + +QString OrderByColumnList::debugString() const +{ + if (isEmpty()) + return "NONE"; + QString dbg; + for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) { + if (!dbg.isEmpty()) + dbg += "\n"; + dbg += (*it).debugString(); + } + return dbg; +} + +QString OrderByColumnList::toSQLString(bool includeTableNames, Driver *drv, int identifierEscaping) const +{ + QString string; + for (OrderByColumn::ListConstIterator it=constBegin(); it!=constEnd(); ++it) { + if (!string.isEmpty()) + string += ", "; + string += (*it).toSQLString(includeTableNames, drv, identifierEscaping); + } + return string; +} + +//======================================= + +QuerySchema::QuerySchema() + : FieldList(false)//fields are not owned by QuerySchema object + , SchemaData(KexiDB::QueryObjectType) + , d( new QuerySchemaPrivate(this) ) +{ + init(); +} + +QuerySchema::QuerySchema(TableSchema& tableSchema) + : FieldList(false) + , SchemaData(KexiDB::QueryObjectType) + , d( new QuerySchemaPrivate(this) ) +{ + d->masterTable = &tableSchema; + init(); +/* if (!d->masterTable) { + KexiDBWarn << "QuerySchema(TableSchema*): !d->masterTable" << endl; + m_name = QString::null; + return; + }*/ + addTable(d->masterTable); + //defaults: + //inherit name from a table + m_name = d->masterTable->name(); + //inherit caption from a table + m_caption = d->masterTable->caption(); + +//replaced by explicit field list: //add all fields of the table as asterisk: +//replaced by explicit field list: addField( new QueryAsterisk(this) ); + + // add explicit field list to avoid problems (e.g. with fields added outside of Kexi): + for (Field::ListIterator it( d->masterTable->fieldsIterator() ); it.current(); ++it) { + addField( it.current() ); + } +} + +QuerySchema::QuerySchema(const QuerySchema& querySchema) + : FieldList(querySchema, false /* !deepCopyFields */) + , SchemaData(querySchema) + , d( new QuerySchemaPrivate(this, querySchema.d) ) +{ + //only deep copy query asterisks + for (Field::ListIterator f_it(querySchema.m_fields); f_it.current(); ++f_it) { + Field *f; + if (dynamic_cast<QueryAsterisk*>( f_it.current() )) { + f = f_it.current()->copy(); + if (static_cast<const KexiDB::FieldList *>(f_it.current()->m_parent) == &querySchema) + f->m_parent = this; + } + else + f = f_it.current(); + addField( f ); + } +} + +QuerySchema::~QuerySchema() +{ + delete d; +} + +void QuerySchema::init() +{ + m_type = KexiDB::QueryObjectType; +//m_fields_by_name.setAutoDelete( true ); //because we're using QueryColumnInfoEntry objects +} + +void QuerySchema::clear() +{ + FieldList::clear(); + SchemaData::clear(); + d->clear(); +} + +FieldList& QuerySchema::insertField(uint position, Field *field, bool visible) +{ + return insertField(position, field, -1/*don't bind*/, visible); +} + +/*virtual*/ +FieldList& QuerySchema::insertField(uint position, Field *field) +{ + return insertField( position, field, -1/*don't bind*/, true ); +} + +FieldList& QuerySchema::insertField(uint position, Field *field, + int bindToTable, bool visible) +{ + if (!field) { + KexiDBWarn << "QuerySchema::insertField(): !field" << endl; + return *this; + } + + if (position>m_fields.count()) { + KexiDBWarn << "QuerySchema::insertField(): position (" << position << ") out of range" << endl; + return *this; + } + if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) { + KexiDBWarn << "QuerySchema::insertField(): WARNING: field '"<<field->name() + <<"' must contain table information!" <<endl; + return *this; + } + if (fieldCount()>=d->visibility.size()) { + d->visibility.resize(d->visibility.size()*2); + d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2); + } + d->clearCachedData(); + FieldList::insertField(position, field); + if (field->isQueryAsterisk()) { + d->asterisks.append(field); + //if this is single-table asterisk, + //add a table to list if doesn't exist there: + if (field->table() && (d->tables.findRef(field->table())==-1)) + d->tables.append(field->table()); + } + else if (field->table()) { + //add a table to list if doesn't exist there: + if (d->tables.findRef(field->table())==-1) + d->tables.append(field->table()); + } +// //visible by default +// setFieldVisible(field, true); +// d->visibility.setBit(fieldCount()-1, visible); + //update visibility + //--move bits to make a place for a new one + for (uint i=fieldCount()-1; i>position; i--) + d->visibility.setBit(i, d->visibility.testBit(i-1)); + d->visibility.setBit(position, visible); + + //bind to table + if (bindToTable < -1 && bindToTable>(int)d->tables.count()) { + KexiDBWarn << "QuerySchema::insertField(): bindToTable (" << bindToTable + << ") out of range" << endl; + bindToTable = -1; + } + //--move items to make a place for a new one + for (uint i=fieldCount()-1; i>position; i--) + d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1]; + d->tablesBoundToColumns[ position ] = bindToTable; + + KexiDBDbg << "QuerySchema::insertField(): bound to table (" << bindToTable << "): " <<endl; + if (bindToTable==-1) + KexiDBDbg << " <NOT SPECIFIED>" << endl; + else + KexiDBDbg << " name=" << d->tables.at(bindToTable)->name() + << " alias=" << tableAlias(bindToTable) << endl; + QString s; + for (uint i=0; i<fieldCount();i++) + s+= (QString::number(d->tablesBoundToColumns[i]) + " "); + KexiDBDbg << "tablesBoundToColumns == [" << s << "]" <<endl; + + if (field->isExpression()) + d->regenerateExprAliases = true; + + return *this; +} + +int QuerySchema::tableBoundToColumn(uint columnPosition) const +{ + if (columnPosition > d->tablesBoundToColumns.count()) { + KexiDBWarn << "QuerySchema::tableBoundToColumn(): columnPosition (" << columnPosition + << ") out of range" << endl; + return -1; + } + return d->tablesBoundToColumns[columnPosition]; +} + +KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, bool visible) +{ + return insertField(m_fields.count(), field, visible); +} + +KexiDB::FieldList& QuerySchema::addField(KexiDB::Field* field, int bindToTable, + bool visible) +{ + return insertField(m_fields.count(), field, bindToTable, visible); +} + +void QuerySchema::removeField(KexiDB::Field *field) +{ + if (!field) + return; + d->clearCachedData(); + if (field->isQueryAsterisk()) { + d->asterisks.remove(field); //this will destroy this asterisk + } +//TODO: should we also remove table for this field or asterisk? + FieldList::removeField(field); +} + +FieldList& QuerySchema::addExpression(BaseExpr* expr, bool visible) +{ + return addField( new Field(this, expr), visible ); +} + +bool QuerySchema::isColumnVisible(uint position) const +{ + return (position < fieldCount()) ? d->visibility.testBit(position) : false; +} + +void QuerySchema::setColumnVisible(uint position, bool v) +{ + if (position < fieldCount()) + d->visibility.setBit(position, v); +} + +FieldList& QuerySchema::addAsterisk(QueryAsterisk *asterisk, bool visible) +{ + if (!asterisk) + return *this; + //make unique name + asterisk->m_name = (asterisk->table() ? asterisk->table()->name() + ".*" : "*") + + QString::number(asterisks()->count()); + return addField(asterisk, visible); +} + +Connection* QuerySchema::connection() const +{ + TableSchema *mt = masterTable(); + return mt ? mt->connection() : 0; +} + +QString QuerySchema::debugString() +{ + QString dbg; + dbg.reserve(1024); + //fields + TableSchema *mt = masterTable(); + dbg = QString("QUERY ") + schemaDataDebugString() + "\n" + + "-masterTable=" + (mt ? mt->name() :"<NULL>") + + "\n-COLUMNS:\n" + + ((fieldCount()>0) ? FieldList::debugString() : "<NONE>") + "\n" + + "-FIELDS EXPANDED "; + + QString dbg1; + uint fieldsExpandedCount = 0; + if (fieldCount()>0) { + QueryColumnInfo::Vector fe( fieldsExpanded() ); + fieldsExpandedCount = fe.size(); + for ( uint i=0; i < fieldsExpandedCount; i++ ) { + QueryColumnInfo *ci = fe[i]; + if (!dbg1.isEmpty()) + dbg1 += ",\n"; + dbg1 += ci->debugString(); + } + dbg1 += "\n"; + } + else { + dbg1 = "<NONE>\n"; + } + dbg1.prepend( QString("(%1):\n").arg(fieldsExpandedCount) ); + dbg += dbg1; + + //it's safer to delete fieldsExpanded for now + // (debugString() could be called before all fields are added) +//causes a crash d->clearCachedData(); + + //bindings + QString dbg2; + dbg2.reserve(512); + for (uint i = 0; i<fieldCount(); i++) { + int tablePos = tableBoundToColumn(i); + if (tablePos>=0) { + QCString tAlias = tableAlias(tablePos); + if (!tAlias.isEmpty()) { + dbg2 += (QString::fromLatin1(" field \"") + FieldList::field(i)->name() + + "\" uses alias \"" + QString(tAlias) + "\" of table \"" + + d->tables.at(tablePos)->name() + "\"\n"); + } + } + } + if (!dbg2.isEmpty()) { + dbg += "\n-BINDINGS:\n"; + dbg += dbg2; + } + + //tables + TableSchema *table; + QString table_names; + table_names.reserve(512); + for ( table = d->tables.first(); table; table = d->tables.next() ) { + if (!table_names.isEmpty()) + table_names += ", "; + table_names += (QString("'") + table->name() + "'"); + } + if (d->tables.isEmpty()) + table_names = "<NONE>"; + dbg += (QString("-TABLES:\n") + table_names); + QString aliases; + if (!d->hasColumnAliases()) + aliases = "<NONE>\n"; + else { + Field::ListIterator it( m_fields ); + for (int i=0; it.current(); ++it, i++) { + QCString *alias = d->columnAlias(i); + if (alias) + aliases += (QString("field #%1: ").arg(i) + + (it.current()->name().isEmpty() ? "<noname>" : it.current()->name()) + + " -> " + (const char*)*alias + "\n"); + } + } + //aliases + dbg += QString("\n-COLUMN ALIASES:\n" + aliases); + if (d->tableAliases.isEmpty()) + aliases = "<NONE>"; + else { + aliases = ""; + TableSchema::ListIterator t_it(d->tables); + for (int i=0; t_it.current(); ++t_it, i++) { + QCString *alias = d->tableAliases[i]; + if (alias) + aliases += (QString("table #%1: ").arg(i) + + (t_it.current()->name().isEmpty() ? "<noname>" : t_it.current()->name()) + + " -> " + (const char*)*alias + "\n"); + } + } + dbg += QString("-TABLE ALIASES:\n" + aliases); + QString where = d->whereExpr ? d->whereExpr->debugString() : QString::null; + if (!where.isEmpty()) + dbg += (QString("\n-WHERE EXPRESSION:\n") + where); + if (!orderByColumnList().isEmpty()) + dbg += (QString("\n-ORDER BY (%1):\n").arg(orderByColumnList().count()) + + orderByColumnList().debugString()); + return dbg; +} + +TableSchema* QuerySchema::masterTable() const +{ + if (d->masterTable) + return d->masterTable; + if (d->tables.isEmpty()) + return 0; + + //try to find master table if there's only one table (with possible aliasses) + int num = 0; + QString tableNameLower; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (!tableNameLower.isEmpty() && it.current()->name().lower()!=tableNameLower) { + //two or more different tables + return 0; + } + tableNameLower = tableAlias(num); + } + return d->tables.first(); +} + +void QuerySchema::setMasterTable(TableSchema *table) +{ + if (table) + d->masterTable=table; +} + +TableSchema::List* QuerySchema::tables() const +{ + return &d->tables; +} + +void QuerySchema::addTable(TableSchema *table, const QCString& alias) +{ + KexiDBDbg << "QuerySchema::addTable() " << (void *)table + << " alias=" << alias << endl; + if (!table) + return; + + //only append table if: + //-it has alias + //-it has no alias but there is no such table on the list + if (alias.isEmpty() && d->tables.findRef(table)!=-1) { + const QString& tableNameLower = table->name().lower(); + const QString& aliasLower = QString(alias.lower()); + int num = 0; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableNameLower) { + const QString& tAlias = tableAlias(num); + if (tAlias == aliasLower) { + KexiDBWarn << "QuerySchema::addTable(): table with \"" + << tAlias << "\" alias already added!" << endl; + return; + } + } + } + } + + d->tables.append(table); + + if (!alias.isEmpty()) + setTableAlias(d->tables.count()-1, alias); +} + +void QuerySchema::removeTable(TableSchema *table) +{ + if (!table) + return; + if (d->masterTable == table) + d->masterTable = 0; + d->tables.remove(table); + //todo: remove fields! +} + +TableSchema* QuerySchema::table(const QString& tableName) const +{ +//TODO: maybe use tables_byname? + for (TableSchema::ListIterator it(d->tables); it.current(); ++it) { + if (it.current()->name().lower()==tableName.lower()) + return it.current(); + } + return 0; +} + +bool QuerySchema::contains(TableSchema *table) const +{ + return d->tables.findRef(table)!=-1; +} + +Field* QuerySchema::findTableField(const QString &tableOrTableAndFieldName) const +{ + QString tableName, fieldName; + if (!KexiDB::splitToTableAndFieldParts(tableOrTableAndFieldName, + tableName, fieldName, KexiDB::SetFieldNameIfNoTableName)) { + return 0; + } + if (tableName.isEmpty()) { + for (TableSchema::ListIterator it(d->tables); it.current(); ++it) { + if (it.current()->field(fieldName)) + return it.current()->field(fieldName); + } + return 0; + } + TableSchema *tableSchema = table(tableName); + if (!tableSchema) + return 0; + return tableSchema->field(fieldName); +} + +QCString QuerySchema::columnAlias(uint position) const +{ + QCString *a = d->columnAlias(position); + return a ? *a : QCString(); +} + +bool QuerySchema::hasColumnAlias(uint position) const +{ + return d->columnAlias(position)!=0; +} + +void QuerySchema::setColumnAlias(uint position, const QCString& alias) +{ + if (position >= m_fields.count()) { + KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position + << ") out of range!" << endl; + return; + } + QCString fixedAlias = alias.stripWhiteSpace(); + Field *f = FieldList::field(position); + if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) { + KexiDBWarn << "QuerySchema::setColumnAlias(): position (" << position + << ") could not remove alias when no name is specified for expression column!" << endl; + return; + } + d->setColumnAlias(position, fixedAlias); +} + +QCString QuerySchema::tableAlias(uint position) const +{ + QCString *a = d->tableAliases[position]; + return a ? *a : QCString(); +} + +int QuerySchema::tablePositionForAlias(const QCString& name) const +{ + int *num = d->tablePositionsForAliases[name]; + if (!num) + return -1; + return *num; +} + +int QuerySchema::tablePosition(const QString& tableName) const +{ + int num = 0; + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableName.lower()) + return num; + } + return -1; +} + +QValueList<int> QuerySchema::tablePositions(const QString& tableName) const +{ + int num = 0; + QValueList<int> result; + const QString& tableNameLower = tableName.lower(); + for (TableSchema::ListIterator it(d->tables); it.current(); ++it, num++) { + if (it.current()->name().lower()==tableNameLower) { + result += num; + } + } + return result; +} + +bool QuerySchema::hasTableAlias(uint position) const +{ + return d->tableAliases[position]!=0; +} + +int QuerySchema::columnPositionForAlias(const QCString& name) const +{ + int *num = d->columnPositionsForAliases[name]; + if (!num) + return -1; + return *num; +} + +void QuerySchema::setTableAlias(uint position, const QCString& alias) +{ + if (position >= d->tables.count()) { + KexiDBWarn << "QuerySchema::setTableAlias(): position (" << position + << ") out of range!" << endl; + return; + } + QCString fixedAlias = alias.stripWhiteSpace(); + if (fixedAlias.isEmpty()) { + QCString *oldAlias = d->tableAliases.take(position); + if (oldAlias) { + d->tablePositionsForAliases.remove(*oldAlias); + delete oldAlias; + } +// d->maxIndexWithTableAlias = -1; + } + else { + d->tableAliases.replace(position, new QCString(fixedAlias)); + d->tablePositionsForAliases.replace(fixedAlias, new int(position)); +// d->maxIndexWithTableAlias = QMAX( d->maxIndexWithTableAlias, (int)index ); + } +} + +Relationship::List* QuerySchema::relationships() const +{ + return &d->relations; +} + +Field::List* QuerySchema::asterisks() const +{ + return &d->asterisks; +} + +QString QuerySchema::statement() const +{ + return d->statement; +} + +void QuerySchema::setStatement(const QString &s) +{ + d->statement = s; +} + +Field* QuerySchema::field(const QString& identifier, bool expanded) +{ + QueryColumnInfo *ci = columnInfo(identifier, expanded); + return ci ? ci->field : 0; +} + +QueryColumnInfo* QuerySchema::columnInfo(const QString& identifier, bool expanded) +{ + computeFieldsExpanded(); + return expanded ? d->columnInfosByNameExpanded[identifier] : d->columnInfosByName[identifier]; +} + +QueryColumnInfo::Vector QuerySchema::fieldsExpanded(FieldsExpandedOptions options) +{ + computeFieldsExpanded(); + if (options == WithInternalFields || options == WithInternalFieldsAndRowID) { + //a ref to a proper pointer (as we cache the vector for two cases) + QueryColumnInfo::Vector*& tmpFieldsExpandedWithInternal = + (options == WithInternalFields) ? d->fieldsExpandedWithInternal : d->fieldsExpandedWithInternalAndRowID; + //special case + if (!tmpFieldsExpandedWithInternal) { + //glue expanded and internal fields and cache it + const uint size = d->fieldsExpanded->count() + + (d->internalFields ? d->internalFields->count() : 0) + + ((options == WithInternalFieldsAndRowID) ? 1 : 0) /*ROWID*/; + tmpFieldsExpandedWithInternal = new QueryColumnInfo::Vector( size ); + const uint fieldsExpandedVectorSize = d->fieldsExpanded->size(); + for (uint i=0; i<fieldsExpandedVectorSize; i++) + tmpFieldsExpandedWithInternal->insert(i, d->fieldsExpanded->at(i)); + const uint internalFieldsCount = d->internalFields ? d->internalFields->size() : 0; + if (internalFieldsCount > 0) { + for (uint i=0; i < internalFieldsCount; i++) + tmpFieldsExpandedWithInternal->insert( + fieldsExpandedVectorSize + i, d->internalFields->at(i)); + } + if (options == WithInternalFieldsAndRowID) { + if (!d->fakeRowIDField) { + d->fakeRowIDField = new Field("rowID", Field::BigInteger); + d->fakeRowIDCol = new QueryColumnInfo(d->fakeRowIDField, QCString(), true); + } + tmpFieldsExpandedWithInternal->insert( + fieldsExpandedVectorSize + internalFieldsCount, d->fakeRowIDCol ); + } + } + return *tmpFieldsExpandedWithInternal; + } + + if (options == Default) + return *d->fieldsExpanded; + + //options == Unique: + QDict<char> columnsAlreadyFound; + QueryColumnInfo::Vector result( d->fieldsExpanded->count() ); //initial size is set +// QMapConstIterator<QueryColumnInfo*, bool> columnsAlreadyFoundIt; + //compute unique list + uint uniqueListCount = 0; + for (uint i=0; i<d->fieldsExpanded->count(); i++) { + QueryColumnInfo *ci = (*d->fieldsExpanded)[i]; +// columnsAlreadyFoundIt = columnsAlreadyFound.find(ci); +// uint foundColumnIndex = -1; + if (!columnsAlreadyFound[ci->aliasOrName()]) {// columnsAlreadyFoundIt==columnsAlreadyFound.constEnd()) + columnsAlreadyFound.insert(ci->aliasOrName(), (char*)1); + result.insert(uniqueListCount++, ci); + } + } + result.resize(uniqueListCount); //update result size + return result; +} + +QueryColumnInfo::Vector QuerySchema::internalFields() +{ + computeFieldsExpanded(); + return d->internalFields ? *d->internalFields : QueryColumnInfo::Vector(); +} + +QueryColumnInfo* QuerySchema::expandedOrInternalField(uint index) +{ + QueryColumnInfo::Vector vector = fieldsExpanded(WithInternalFields); + return (index < vector.size()) ? vector[index] : 0; +} + +inline QString lookupColumnKey(Field *foreignField, Field* field) +{ + QString res; + if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns + res = field->table()->name() + "."; + return res + field->name() + "_" + foreignField->table()->name() + "." + foreignField->name(); +} + +void QuerySchema::computeFieldsExpanded() +{ + if (d->fieldsExpanded) + return; + + if (!d->columnsOrder) { + d->columnsOrder = new QMap<QueryColumnInfo*,int>(); + d->columnsOrderWithoutAsterisks = new QMap<QueryColumnInfo*,int>(); + } + else { + d->columnsOrder->clear(); + d->columnsOrderWithoutAsterisks->clear(); + } + if (d->ownedVisibleColumns) + d->ownedVisibleColumns->clear(); + + //collect all fields in a list (not a vector yet, because we do not know its size) + QueryColumnInfo::List list; //temporary + QueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields + QMap<QueryColumnInfo*, bool> columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName + uint i = 0; + uint fieldPosition = 0; + uint numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field + Field *f; + for (Field::ListIterator it = fieldsIterator(); (f = it.current()); ++it, fieldPosition++) { + if (f->isQueryAsterisk()) { + if (static_cast<QueryAsterisk*>(f)->isSingleTableAsterisk()) { + Field::List *ast_fields = static_cast<QueryAsterisk*>(f)->table()->fields(); + for (Field *ast_f = ast_fields->first(); ast_f; ast_f=ast_fields->next()) { +// d->detailedVisibility += isFieldVisible(fieldPosition); + QueryColumnInfo *ci = new QueryColumnInfo(ast_f, QCString()/*no field for asterisk!*/, + isColumnVisible(fieldPosition)); + list.append( ci ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); +// list.append(ast_f); + } + } + else {//all-tables asterisk: iterate through table list + for (TableSchema *table = d->tables.first(); table; table = d->tables.next()) { + //add all fields from this table + Field::List *tab_fields = table->fields(); + for (Field *tab_f = tab_fields->first(); tab_f; tab_f = tab_fields->next()) { +//! \todo (js): perhaps not all fields should be appended here +// d->detailedVisibility += isFieldVisible(fieldPosition); +// list.append(tab_f); + QueryColumnInfo *ci = new QueryColumnInfo(tab_f, QCString()/*no field for asterisk!*/, + isColumnVisible(fieldPosition)); + list.append( ci ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) columns order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); + } + } + } + } + else { + //a single field +// d->detailedVisibility += isFieldVisible(fieldPosition); + QueryColumnInfo *ci = new QueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition)); + list.append( ci ); + columnInfosOutsideAsterisks.insert( ci, true ); + KexiDBDbg << "QuerySchema::computeFieldsExpanded(): caching (unexpanded) column's order: " + << ci->debugString() << " at position " << fieldPosition << endl; + d->columnsOrder->insert(ci, fieldPosition); + d->columnsOrderWithoutAsterisks->insert(ci, fieldPosition); + + //handle lookup field schema + LookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema( *f ) : 0; + if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0) + continue; + // Lookup field schema found: + // Now we also need to fetch "visible" value from the lookup table, not only the value of binding. + // -> build LEFT OUTER JOIN clause for this purpose (LEFT, not INNER because the binding can be broken) + // "LEFT OUTER JOIN lookupTable ON thisTable.thisField=lookupTable.boundField" + LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource(); + if (rowSource.type() == LookupFieldSchema::RowSource::Table) { + TableSchema *lookupTable = connection()->tableSchema( rowSource.name() ); + FieldList* visibleColumns = 0; + Field *boundField = 0; + if (lookupTable + && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount() + && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() )) + && (boundField = lookupTable->field( lookupFieldSchema->boundColumn() ))) + { + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns->fieldCount() == 1) { + visibleColumn = visibleColumns->fields()->first(); + } + else { + // for multiple visible columns, build an expression column + // (the expression object will be owned by column info) + visibleColumn = new Field(); + visibleColumn->setName( + QString::fromLatin1("[multiple_visible_fields_%1]") + .arg( ++numberOfColumnsWithMultipleVisibleFields )); + visibleColumn->setExpression( + new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); + if (!d->ownedVisibleColumns) { + d->ownedVisibleColumns = new Field::List(); + d->ownedVisibleColumns->setAutoDelete(true); + } + d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later + } + + lookup_list.append( + new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) ); +/* + //add visibleField to the list of SELECTed fields if it is not yes present there + if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { + if (!table( visibleField->table()->name() )) { + } + if (!sql.isEmpty()) + sql += QString::fromLatin1(", "); + sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + + escapeIdentifier(visibleField->name(), drvEscaping)); + }*/ + } + delete visibleColumns; + } + else if (rowSource.type() == LookupFieldSchema::RowSource::Query) { + QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() ); + if (!lookupQuery) + continue; + const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() ); + if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) + continue; + QueryColumnInfo *boundColumnInfo = 0; + if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ])) + continue; + Field *boundField = boundColumnInfo->field; + if (!boundField) + continue; + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + bool ok = true; + // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1] + foreach (QValueList<uint>::ConstIterator, visibleColumnsIt, visibleColumns) { + if ((*visibleColumnsIt) >= lookupQueryFieldsExpanded.count()) { + ok = false; + break; + } + } + if (!ok) + continue; + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns.count() == 1) { + visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field; + } + else { + // for multiple visible columns, build an expression column + // (the expression object will be owned by column info) + visibleColumn = new Field(); + visibleColumn->setName( + QString::fromLatin1("[multiple_visible_fields_%1]") + .arg( ++numberOfColumnsWithMultipleVisibleFields )); + visibleColumn->setExpression( + new ConstExpr(CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); + if (!d->ownedVisibleColumns) { + d->ownedVisibleColumns = new Field::List(); + d->ownedVisibleColumns->setAutoDelete(true); + } + d->ownedVisibleColumns->append( visibleColumn ); // remember to delete later + } + + lookup_list.append( + new QueryColumnInfo(visibleColumn, QCString(), true/*visible*/, ci/*foreign*/) ); +/* + //add visibleField to the list of SELECTed fields if it is not yes present there + if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { + if (!table( visibleField->table()->name() )) { + } + if (!sql.isEmpty()) + sql += QString::fromLatin1(", "); + sql += (escapeIdentifier(visibleField->table()->name(), drvEscaping) + "." + + escapeIdentifier(visibleField->name(), drvEscaping)); + }*/ + } + } + } + //prepare clean vector for expanded list, and a map for order information + if (!d->fieldsExpanded) { + d->fieldsExpanded = new QueryColumnInfo::Vector( list.count() );// Field::Vector( list.count() ); + d->fieldsExpanded->setAutoDelete(true); + d->columnsOrderExpanded = new QMap<QueryColumnInfo*,int>(); + } + else {//for future: + d->fieldsExpanded->clear(); + d->fieldsExpanded->resize( list.count() ); + d->columnsOrderExpanded->clear(); + } + + /*fill (based on prepared 'list' and 'lookup_list'): + -the vector + -the map + -"fields by name" dictionary + */ + d->columnInfosByName.clear(); + d->columnInfosByNameExpanded.clear(); + i=0; + QueryColumnInfo *ci; + for (QueryColumnInfo::ListIterator it(list); (ci = it.current()); ++it, i++) { + d->fieldsExpanded->insert(i, ci); + d->columnsOrderExpanded->insert(ci, i); + //remember field by name/alias/table.name if there's no such string yet in d->columnInfosByNameExpanded + if (!ci->alias.isEmpty()) { + //store alias and table.alias + if (!d->columnInfosByNameExpanded[ ci->alias ]) + d->columnInfosByNameExpanded.insert( ci->alias, ci ); + QString tableAndAlias( ci->alias ); + if (ci->field->table()) + tableAndAlias.prepend(ci->field->table()->name() + "."); + if (!d->columnInfosByNameExpanded[ tableAndAlias ]) + d->columnInfosByNameExpanded.insert( tableAndAlias, ci ); + //the same for "unexpanded" list + if (columnInfosOutsideAsterisks.contains(ci)) { + if (!d->columnInfosByName[ ci->alias ]) + d->columnInfosByName.insert( ci->alias, ci ); + if (!d->columnInfosByName[ tableAndAlias ]) + d->columnInfosByName.insert( tableAndAlias, ci ); + } + } + else { + //no alias: store name and table.name + if (!d->columnInfosByNameExpanded[ ci->field->name() ]) + d->columnInfosByNameExpanded.insert( ci->field->name(), ci ); + QString tableAndName( ci->field->name() ); + if (ci->field->table()) + tableAndName.prepend(ci->field->table()->name() + "."); + if (!d->columnInfosByNameExpanded[ tableAndName ]) + d->columnInfosByNameExpanded.insert( tableAndName, ci ); + //the same for "unexpanded" list + if (columnInfosOutsideAsterisks.contains(ci)) { + if (!d->columnInfosByName[ ci->field->name() ]) + d->columnInfosByName.insert( ci->field->name(), ci ); + if (!d->columnInfosByName[ tableAndName ]) + d->columnInfosByName.insert( tableAndName, ci ); + } + } + } + + //remove duplicates for lookup fields + QDict<uint> lookup_dict(101); //used to fight duplicates and to update QueryColumnInfo::indexForVisibleLookupValue() + // (a mapping from table.name string to uint* lookupFieldIndex + lookup_dict.setAutoDelete(true); + i=0; + for (QueryColumnInfo::ListIterator it(lookup_list); (ci = it.current());) + { + const QString key( lookupColumnKey(ci->foreignColumn()->field, ci->field) ); + if ( /* not needed columnInfo( tableAndFieldName ) || */ + lookup_dict[ key ]) { + // this table.field is already fetched by this query + ++it; + lookup_list.removeRef( ci ); + } + else { + lookup_dict.replace( key, new uint( i ) ); + ++it; + i++; + } + } + + //create internal expanded list with lookup fields + if (d->internalFields) { + d->internalFields->clear(); + d->internalFields->resize( lookup_list.count() ); + } + delete d->fieldsExpandedWithInternal; //clear cache + delete d->fieldsExpandedWithInternalAndRowID; //clear cache + d->fieldsExpandedWithInternal = 0; + d->fieldsExpandedWithInternalAndRowID = 0; + if (!lookup_list.isEmpty() && !d->internalFields) {//create on demand + d->internalFields = new QueryColumnInfo::Vector( lookup_list.count() ); + d->internalFields->setAutoDelete(true); + } + i=0; + for (QueryColumnInfo::ListIterator it(lookup_list); it.current();i++, ++it) + { + //add it to the internal list + d->internalFields->insert(i, it.current()); + d->columnsOrderExpanded->insert(it.current(), list.count()+i); + } + + //update QueryColumnInfo::indexForVisibleLookupValue() cache for columns + numberOfColumnsWithMultipleVisibleFields = 0; + for (i=0; i < d->fieldsExpanded->size(); i++) { + QueryColumnInfo* ci = d->fieldsExpanded->at(i); +//! @todo QuerySchema itself will also support lookup fields... + LookupFieldSchema *lookupFieldSchema + = ci->field->table() ? ci->field->table()->lookupFieldSchema( *ci->field ) : 0; + if (!lookupFieldSchema || lookupFieldSchema->boundColumn()<0) + continue; + LookupFieldSchema::RowSource& rowSource = lookupFieldSchema->rowSource(); + if (rowSource.type() == LookupFieldSchema::RowSource::Table) { + TableSchema *lookupTable = connection()->tableSchema( rowSource.name() ); + FieldList* visibleColumns = 0; + if (lookupTable + && (uint)lookupFieldSchema->boundColumn() < lookupTable->fieldCount() + && (visibleColumns = lookupTable->subList( lookupFieldSchema->visibleColumns() ))) + { + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns->fieldCount() == 1) + { + visibleColumn = visibleColumns->fields()->first(); + const QString key( lookupColumnKey(ci->field, visibleColumn) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + else { + const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") + .arg( ++numberOfColumnsWithMultipleVisibleFields ) + .arg(ci->field->table()->name()).arg(ci->field->name()) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + } + delete visibleColumns; + } + else if (rowSource.type() == LookupFieldSchema::RowSource::Query) { + QuerySchema *lookupQuery = connection()->querySchema( rowSource.name() ); + if (!lookupQuery) + continue; + const QueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded() ); + if ((uint)lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) + continue; + QueryColumnInfo *boundColumnInfo = 0; + if (!(boundColumnInfo = lookupQueryFieldsExpanded[ lookupFieldSchema->boundColumn() ])) + continue; + Field *boundField = boundColumnInfo->field; + if (!boundField) + continue; + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + Field *visibleColumn = 0; + // for single visible column, just add it as-is + if (visibleColumns.count() == 1) { + visibleColumn = lookupQueryFieldsExpanded[ visibleColumns.first() ]->field; + const QString key( lookupColumnKey(ci->field, visibleColumn) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + else { + const QString key( QString::fromLatin1("[multiple_visible_fields_%1]_%2.%3") + .arg( ++numberOfColumnsWithMultipleVisibleFields ) + .arg(ci->field->table()->name()).arg(ci->field->name()) ); + uint *index = lookup_dict[ key ]; + if (index) + ci->setIndexForVisibleLookupValue( d->fieldsExpanded->size() + *index ); + } + } + else { + KexiDBWarn << "QuerySchema::computeFieldsExpanded(): unsupported row source type " + << rowSource.typeName() << endl; + } + } +} + +QMap<QueryColumnInfo*,int> QuerySchema::columnsOrder(ColumnsOrderOptions options) +{ + if (!d->columnsOrder) + computeFieldsExpanded(); + if (options == UnexpandedList) + return *d->columnsOrder; + else if (options == UnexpandedListWithoutAsterisks) + return *d->columnsOrderWithoutAsterisks; + return *d->columnsOrderExpanded; +} + +QValueVector<int> QuerySchema::pkeyFieldsOrder() +{ + if (d->pkeyFieldsOrder) + return *d->pkeyFieldsOrder; + + TableSchema *tbl = masterTable(); + if (!tbl || !tbl->primaryKey()) + return QValueVector<int>(); + + //get order of PKEY fields (e.g. for rows updating or inserting ) + IndexSchema *pkey = tbl->primaryKey(); + pkey->debug(); + debug(); + d->pkeyFieldsOrder = new QValueVector<int>( pkey->fieldCount(), -1 ); + + const uint fCount = fieldsExpanded().count(); + d->pkeyFieldsCount = 0; + for (uint i = 0; i<fCount; i++) { + QueryColumnInfo *fi = d->fieldsExpanded->at(i); + const int fieldIndex = fi->field->table()==tbl ? pkey->indexOf(fi->field) : -1; + if (fieldIndex!=-1/* field found in PK */ + && d->pkeyFieldsOrder->at(fieldIndex)==-1 /* first time */) + { + KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): FIELD " << fi->field->name() + << " IS IN PKEY AT POSITION #" << fieldIndex << endl; +// (*d->pkeyFieldsOrder)[j]=i; + (*d->pkeyFieldsOrder)[fieldIndex]=i; + d->pkeyFieldsCount++; +// j++; + } + } + KexiDBDbg << "QuerySchema::pkeyFieldsOrder(): " << d->pkeyFieldsCount + << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name() << endl; + return *d->pkeyFieldsOrder; +} + +uint QuerySchema::pkeyFieldsCount() +{ + (void)pkeyFieldsOrder(); /* rebuild information */ + return d->pkeyFieldsCount; +} + +Relationship* QuerySchema::addRelationship( Field *field1, Field *field2 ) +{ +//@todo: find existing global db relationships + Relationship *r = new Relationship(this, field1, field2); + if (r->isEmpty()) { + delete r; + return 0; + } + + d->relations.append( r ); + return r; +} + +QueryColumnInfo::List* QuerySchema::autoIncrementFields() +{ + if (!d->autoincFields) { + d->autoincFields = new QueryColumnInfo::List(); + } + TableSchema *mt = masterTable(); + if (!mt) { + KexiDBWarn << "QuerySchema::autoIncrementFields(): no master table!" << endl; + return d->autoincFields; + } + if (d->autoincFields->isEmpty()) {//no cache + QueryColumnInfo::Vector fexp = fieldsExpanded(); + for (int i=0; i<(int)fexp.count(); i++) { + QueryColumnInfo *fi = fexp[i]; + if (fi->field->table() == mt && fi->field->isAutoIncrement()) { + d->autoincFields->append( fi ); + } + } + } + return d->autoincFields; +} + +QString QuerySchema::sqlColumnsList(QueryColumnInfo::List* infolist, Driver *driver) +{ + if (!infolist) + return QString::null; + QString result; + result.reserve(256); + QueryColumnInfo::ListIterator it( *infolist ); + bool start = true; + for (; it.current(); ++it) { + if (!start) + result += ","; + else + start = false; + result += driver->escapeIdentifier( it.current()->field->name() ); + } + return result; +} + +QString QuerySchema::autoIncrementSQLFieldsList(Driver *driver) +{ + if ((Driver *)d->lastUsedDriverForAutoIncrementSQLFieldsList != driver + || d->autoIncrementSQLFieldsList.isEmpty()) + { + d->autoIncrementSQLFieldsList = QuerySchema::sqlColumnsList( autoIncrementFields(), driver ); + d->lastUsedDriverForAutoIncrementSQLFieldsList = driver; + } + return d->autoIncrementSQLFieldsList; +} + +void QuerySchema::setWhereExpression(BaseExpr *expr) +{ + delete d->whereExpr; + d->whereExpr = expr; +} + +void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value, int relation) +{ + int token; + if (value.isNull()) + token = SQL_NULL; + else if (field->isIntegerType()) { + token = INTEGER_CONST; + } + else if (field->isFPNumericType()) { + token = REAL_CONST; + } + else { + token = CHARACTER_STRING_LITERAL; +//! @todo date, time + } + + BinaryExpr * newExpr = new BinaryExpr( + KexiDBExpr_Relational, + new ConstExpr( token, value ), + relation, + new VariableExpr((field->table() ? (field->table()->name()+".") : QString::null)+field->name()) + ); + if (d->whereExpr) { + d->whereExpr = new BinaryExpr( + KexiDBExpr_Logical, + d->whereExpr, + AND, + newExpr + ); + } + else { + d->whereExpr = newExpr; + } +} + +/* +void QuerySchema::addToWhereExpression(KexiDB::Field *field, const QVariant& value) + switch (value.type()) { + case Int: case UInt: case Bool: case LongLong: case ULongLong: + token = INTEGER_CONST; + break; + case Double: + token = REAL_CONST; + break; + default: + token = CHARACTER_STRING_LITERAL; + } +//! @todo date, time + +*/ + +BaseExpr *QuerySchema::whereExpression() const +{ + return d->whereExpr; +} + +void QuerySchema::setOrderByColumnList(const OrderByColumnList& list) +{ + d->orderByColumnList = list; +// all field names should be found, exit otherwise ..........? +} + +OrderByColumnList& QuerySchema::orderByColumnList() const +{ + return d->orderByColumnList; +} + +QuerySchemaParameterList QuerySchema::parameters() +{ + if (!whereExpression()) + return QuerySchemaParameterList(); + QuerySchemaParameterList params; + whereExpression()->getQueryParameters(params); + return params; +} + +/* + new field1, Field *field2 + if (!field1 || !field2) { + KexiDBWarn << "QuerySchema::addRelationship(): !masterField || !detailsField" << endl; + return; + } + if (field1->isQueryAsterisk() || field2->isQueryAsterisk()) { + KexiDBWarn << "QuerySchema::addRelationship(): relationship's fields cannot be asterisks" << endl; + return; + } + if (!hasField(field1) && !hasField(field2)) { + KexiDBWarn << "QuerySchema::addRelationship(): fields do not belong to this query" << endl; + return; + } + if (field1->table() == field2->table()) { + KexiDBWarn << "QuerySchema::addRelationship(): fields cannot belong to the same table" << endl; + return; + } +//@todo: check more things: -types +//@todo: find existing global db relationships + + Field *masterField = 0, *detailsField = 0; + IndexSchema *masterIndex = 0, *detailsIndex = 0; + if (field1->isPrimaryKey() && field2->isPrimaryKey()) { + //2 primary keys + masterField = field1; + masterIndex = masterField->table()->primaryKey(); + detailsField = field2; + detailsIndex = masterField->table()->primaryKey(); + } + else if (field1->isPrimaryKey()) { + masterField = field1; + masterIndex = masterField->table()->primaryKey(); + detailsField = field2; +//@todo: check if it already exists + detailsIndex = new IndexSchema(detailsField->table()); + detailsIndex->addField(detailsField); + detailsIndex->setForeigKey(true); + // detailsField->setForeignKey(true); + } + else if (field2->isPrimaryKey()) { + detailsField = field1; + masterField = field2; + masterIndex = masterField->table()->primaryKey(); +//@todo + } + + if (!masterIndex || !detailsIndex) + return; //failed + + Relationship *rel = new Relationship(masterIndex, detailsIndex); + + d->relations.append( rel ); +}*/ + +//--------------------------------------------------- + +QueryAsterisk::QueryAsterisk( QuerySchema *query, TableSchema *table ) + :Field() + ,m_table(table) +{ + assert(query); + m_parent = query; + setType(Field::Asterisk); +} + +QueryAsterisk::~QueryAsterisk() +{ +} + +Field* QueryAsterisk::copy() const +{ + return new QueryAsterisk(*this); +} + +void QueryAsterisk::setTable(TableSchema *table) +{ + KexiDBDbg << "QueryAsterisk::setTable()" << endl; + m_table=table; +} + +QString QueryAsterisk::debugString() const +{ + QString dbg; + if (isAllTableAsterisk()) { + dbg += "ALL-TABLES ASTERISK (*) ON TABLES("; + TableSchema *table; + QString table_names; + for (TableSchema::ListIterator it( *query()->tables() ); (table = it.current()); ++it) { + if (!table_names.isEmpty()) + table_names += ", "; + table_names += table->name(); + } + dbg += (table_names + ")"); + } + else { + dbg += ("SINGLE-TABLE ASTERISK (" + table()->name() + ".*)"); + } + return dbg; +} + |