diff --git a/src/KDbQuerySchema.cpp b/src/KDbQuerySchema.cpp index be69522a..fd233327 100644 --- a/src/KDbQuerySchema.cpp +++ b/src/KDbQuerySchema.cpp @@ -1,1716 +1,1739 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2017 Jarosław Staniek 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 "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbQuerySchemaParameter.h" #include "KDbConnection.h" #include "KDbLookupFieldSchema.h" #include "KDbParser_p.h" #include "KDbRelationship.h" #include "kdb_debug.h" KDbOrderByColumn::KDbOrderByColumn() : m_column(0) , m_pos(-1) , m_field(0) , m_ascending(true) { } KDbOrderByColumn::KDbOrderByColumn(KDbQueryColumnInfo* column, bool ascending, int pos) : m_column(column) , m_pos(pos) , m_field(0) , m_ascending(ascending) { Q_ASSERT(column); } KDbOrderByColumn::KDbOrderByColumn(KDbField* field, bool ascending) : m_column(0) , m_pos(-1) , m_field(field) , m_ascending(ascending) { Q_ASSERT(field); } KDbOrderByColumn* KDbOrderByColumn::copy(KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) const { if (m_field) { return new KDbOrderByColumn(m_field, m_ascending); } if (m_column) { KDbQueryColumnInfo* columnInfo; if (fromQuery && toQuery) { int columnIndex = fromQuery->columnsOrder().value(m_column); if (columnIndex < 0) { kdbDebug() << "KDbOrderByColumn::copy(): Index not found for column" << *m_column; return 0; } columnInfo = toQuery->expandedOrInternalField(columnIndex); if (!columnInfo) { kdbDebug() << "KDbOrderByColumn::copy(): Column info not found at index" << columnIndex << "in toQuery"; return 0; } } else { columnInfo = m_column; } return new KDbOrderByColumn(columnInfo, m_ascending, m_pos); } Q_ASSERT(m_field || m_column); return 0; } KDbOrderByColumn::~KDbOrderByColumn() { } KDbQueryColumnInfo* KDbOrderByColumn::column() const { return m_column; } int KDbOrderByColumn::position() const { return m_pos; } KDbField* KDbOrderByColumn::field() const { return m_field; } bool KDbOrderByColumn::ascending() const { return m_ascending; } bool KDbOrderByColumn::operator== (const KDbOrderByColumn& col) const { return m_column == col.m_column && m_field == col.m_field && m_ascending == col.m_ascending; } QDebug operator<<(QDebug dbg, const KDbOrderByColumn& order) { const QLatin1String orderString(order.ascending() ? "ASCENDING" : "DESCENDING"); if (order.column()) { if (order.position() > -1) { dbg.nospace() << QString::fromLatin1("COLUMN_AT_POSITION_%1(").arg(order.position() + 1); dbg.space() << *order.column() << ','; dbg.space() << orderString << ')'; return dbg.space(); } else { dbg.nospace() << "COLUMN(" << *order.column() << ','; dbg.space() << orderString << ')'; return dbg.space(); } } if (order.field()) { dbg.nospace() << "FIELD(" << *order.field() << ','; dbg.space() << orderString << ')'; return dbg.space(); } dbg.nospace() << "NONE"; return dbg.space(); } static QString escapeIdentifier(const QString& name, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) { switch (escapingType) { case KDb::DriverEscaping: if (conn) return conn->escapeIdentifier(name); break; case KDb::KDbEscaping: return KDb::escapeIdentifier(name); } return QLatin1Char('"') + name + QLatin1Char('"'); } KDbEscapedString KDbOrderByColumn::toSQLString(bool includeTableName, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) const { const QByteArray orderString(m_ascending ? "" : " DESC"); KDbEscapedString fieldName, tableName, collationString; if (m_column) { if (m_pos > -1) return KDbEscapedString::number(m_pos + 1) + orderString; else { if (includeTableName && m_column->alias.isEmpty()) { tableName = KDbEscapedString(escapeIdentifier(m_column->field->table()->name(), conn, escapingType)); tableName += '.'; } fieldName = KDbEscapedString(escapeIdentifier(m_column->aliasOrName(), conn, escapingType)); } if (m_column->field->isTextType()) { collationString = conn->driver()->collationSQL(); } } else { if (m_field && includeTableName) { tableName = KDbEscapedString(escapeIdentifier(m_field->table()->name(), conn, escapingType)); tableName += '.'; } fieldName = KDbEscapedString(escapeIdentifier( m_field ? m_field->name() : QLatin1String("??")/*error*/, conn, escapingType)); if (m_field && m_field->isTextType()) { collationString = conn->driver()->collationSQL(); } } return tableName + fieldName + collationString + orderString; } //======================================= KDbOrderByColumnList::KDbOrderByColumnList() : QList() { } KDbOrderByColumnList::KDbOrderByColumnList(const KDbOrderByColumnList& other, KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) : QList() { for (QList::ConstIterator it(other.constBegin()); it != other.constEnd(); ++it) { KDbOrderByColumn* order = (*it)->copy(fromQuery, toQuery); if (order) { append(order); } } } bool KDbOrderByColumnList::appendFields(KDbQuerySchema* 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) { Q_ASSERT(querySchema); int 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 (int i = 0; i < numAdded; i++) removeLast(); return false; } KDbOrderByColumnList::~KDbOrderByColumnList() { qDeleteAll(begin(), end()); } void KDbOrderByColumnList::appendColumn(KDbQueryColumnInfo* columnInfo, bool ascending) { Q_ASSERT(columnInfo); append(new KDbOrderByColumn(columnInfo, ascending)); } bool KDbOrderByColumnList::appendColumn(KDbQuerySchema* querySchema, bool ascending, int pos) { Q_ASSERT(querySchema); KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded()); if (pos < 0 || pos >= fieldsExpanded.size()) { return false; } KDbQueryColumnInfo* ci = fieldsExpanded[pos]; append(new KDbOrderByColumn(ci, ascending, pos)); return true; } void KDbOrderByColumnList::appendField(KDbField* field, bool ascending) { Q_ASSERT(field); append(new KDbOrderByColumn(field, ascending)); } bool KDbOrderByColumnList::appendField(KDbQuerySchema* querySchema, const QString& fieldName, bool ascending) { Q_ASSERT(querySchema); KDbQueryColumnInfo *columnInfo = querySchema->columnInfo(fieldName); if (columnInfo) { append(new KDbOrderByColumn(columnInfo, ascending)); return true; } KDbField *field = querySchema->findTableField(fieldName); if (field) { append(new KDbOrderByColumn(field, ascending)); return true; } kdbWarning() << "no such field" << fieldName; return false; } bool KDbOrderByColumnList::isEmpty() const { return QList::isEmpty(); } int KDbOrderByColumnList::count() const { return QList::count(); } KDbOrderByColumnList::iterator KDbOrderByColumnList::begin() { return QList::begin(); } KDbOrderByColumnList::iterator KDbOrderByColumnList::end() { return QList::end(); } KDbOrderByColumnList::const_iterator KDbOrderByColumnList::constBegin() const { return QList::constBegin(); } KDbOrderByColumnList::const_iterator KDbOrderByColumnList::constEnd() const { return QList::constEnd(); } QDebug operator<<(QDebug dbg, const KDbOrderByColumnList& list) { if (list.isEmpty()) { dbg.nospace() << "NONE"; return dbg.space(); } bool first = true; for (QList::ConstIterator it(list.constBegin()); it != list.constEnd(); ++it) { if (first) first = false; else dbg.nospace() << '\n'; dbg.nospace() << *(*it); } return dbg.space(); } KDbEscapedString KDbOrderByColumnList::toSQLString(bool includeTableNames, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) const { KDbEscapedString string; for (QList::ConstIterator it(constBegin()); it != constEnd(); ++it) { if (!string.isEmpty()) string += ", "; string += (*it)->toSQLString(includeTableNames, conn, escapingType); } return string; } void KDbOrderByColumnList::clear() { qDeleteAll(begin(), end()); QList::clear(); } //======================================= KDbQuerySchema::KDbQuerySchema() : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { init(); } KDbQuerySchema::KDbQuerySchema(KDbTableSchema *tableSchema) : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { Q_ASSERT(tableSchema); d->masterTable = tableSchema; d->conn = tableSchema->connection(); init(); /*if (!d->masterTable) { kdbWarning() << "!d->masterTable"; m_name.clear(); return; }*/ addTable(d->masterTable); //defaults: //inherit name from a table setName(d->masterTable->name()); //inherit caption from a table setCaption(d->masterTable->caption()); // add explicit field list to avoid problems (e.g. with fields added outside of the app): foreach(KDbField* f, *d->masterTable->fields()) { addField(f); } } KDbQuerySchema::KDbQuerySchema(const KDbQuerySchema& querySchema) : KDbFieldList(querySchema, false /* !deepCopyFields */) , KDbObject(querySchema) , d(new Private(this, querySchema.d)) { //only deep copy query asterisks foreach(KDbField* f, *querySchema.fields()) { KDbField *copiedField; if (dynamic_cast(f)) { copiedField = f->copy(); if (static_cast(f->m_parent) == &querySchema) { copiedField->m_parent = this; } } else { copiedField = f; } addField(copiedField); } // this deep copy must be after the 'd' initialization because fieldsExpanded() is used there d->orderByColumnList = new KDbOrderByColumnList(*querySchema.d->orderByColumnList, const_cast(&querySchema), this); } KDbQuerySchema::KDbQuerySchema(KDbConnection *conn) : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new Private(this)) { Q_ASSERT(conn); init(); d->conn = conn; } KDbQuerySchema::~KDbQuerySchema() { delete d; } void KDbQuerySchema::init() { //m_fields_by_name.setAutoDelete( true ); //because we're using QueryColumnInfoEntry objects } void KDbQuerySchema::clear() { KDbFieldList::clear(); KDbObject::clear(); d->clear(); } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::insertField(int position, KDbField *field, bool visible) { return insertField(position, field, -1/*don't bind*/, visible); } /*virtual*/ bool KDbQuerySchema::insertField(int position, KDbField *field) { return insertField(position, field, -1/*don't bind*/, true); } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::insertField(int position, KDbField *field, int bindToTable, bool visible) { if (!field) { kdbWarning() << "!field"; return false; } if (position > m_fields.count()) { kdbWarning() << "position" << position << "out of range"; return false; } if (!field->isQueryAsterisk() && !field->isExpression() && !field->table()) { kdbWarning() << "field" << field->name() << "must contain table information!"; return false; } if (fieldCount() >= d->visibility.size()) { d->visibility.resize(d->visibility.size()*2); d->tablesBoundToColumns.resize(d->tablesBoundToColumns.size()*2); } d->clearCachedData(); if (!KDbFieldList::insertField(position, field)) { return false; } 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.contains(field->table())) d->tables.append(field->table()); } else if (field->table()) { //add a table to list if doesn't exist there: if (!d->tables.contains(field->table())) d->tables.append(field->table()); } //update visibility //--move bits to make a place for a new one for (int 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())) { kdbWarning() << "bindToTable" << bindToTable << "out of range"; bindToTable = -1; } //--move items to make a place for a new one for (int i = fieldCount() - 1; i > position; i--) d->tablesBoundToColumns[i] = d->tablesBoundToColumns[i-1]; d->tablesBoundToColumns[position] = bindToTable; kdbDebug() << "bound to table" << bindToTable; if (bindToTable == -1) kdbDebug() << " "; else kdbDebug() << " name=" << d->tables.at(bindToTable)->name() << " alias=" << tableAlias(bindToTable); QString s; for (int i = 0; i < fieldCount();i++) s += (QString::number(d->tablesBoundToColumns[i]) + QLatin1Char(' ')); kdbDebug() << "tablesBoundToColumns == [" << s << "]"; if (field->isExpression()) d->regenerateExprAliases = true; return true; } int KDbQuerySchema::tableBoundToColumn(int columnPosition) const { int res = d->tablesBoundToColumns.value(columnPosition, -99); if (res == -99) { kdbWarning() << "columnPosition" << columnPosition << "out of range"; return -1; } return res; } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::addField(KDbField* field, bool visible) { return insertField(m_fields.count(), field, visible); } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::addField(KDbField* field, int bindToTable, bool visible) { return insertField(m_fields.count(), field, bindToTable, visible); } bool KDbQuerySchema::removeField(KDbField *field) { int indexOfAsterisk = -1; if (field->isQueryAsterisk()) { indexOfAsterisk = d->asterisks.indexOf(field); } if (!KDbFieldList::removeField(field)) { return false; } d->clearCachedData(); if (indexOfAsterisk >= 0) { //kdbDebug() << "d->asterisks.removeAt:" << field; //field->debug(); d->asterisks.removeAt(indexOfAsterisk); //this will destroy this asterisk } //! @todo should we also remove table for this field or asterisk? return true; } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::addExpression(const KDbExpression& expr, bool visible) { KDbField *field = new KDbField(this, expr); if (!addField(field, visible)) { delete field; return false; } return true; } bool KDbQuerySchema::isColumnVisible(int position) const { return (position < fieldCount()) ? d->visibility.testBit(position) : false; } void KDbQuerySchema::setColumnVisible(int position, bool v) { if (position < fieldCount()) d->visibility.setBit(position, v); } //! @todo IMPORTANT: move visible to overload bool KDbQuerySchema::addAsterisk(KDbQueryAsterisk *asterisk, bool visible) { if (!asterisk) { return false; } //make unique name asterisk->setName((asterisk->table() ? (asterisk->table()->name() + QLatin1String(".*")) : QString(QLatin1Char('*'))) + QString::number(asterisks()->count())); return addField(asterisk, visible); } KDbConnection* KDbQuerySchema::connection() const { if (d->conn) { return d->conn; } if (!d->tables.isEmpty()) { return d->tables.first()->connection(); } return 0; } QDebug operator<<(QDebug dbg, const KDbQuerySchema& query) { //fields KDbTableSchema *mt = query.masterTable(); dbg.nospace() << "QUERY"; dbg.space() << static_cast(query) << '\n'; dbg.nospace() << " - MASTERTABLE=" << (mt ? mt->name() : QLatin1String("")) << "\n - COLUMNS:\n"; if (query.fieldCount() > 0) dbg.nospace() << static_cast(query) << '\n'; else dbg.nospace() << "\n"; if (query.fieldCount() == 0) dbg.nospace() << " - NO FIELDS\n"; else dbg.nospace() << " - FIELDS EXPANDED ("; int fieldsExpandedCount = 0; bool first; if (query.fieldCount() > 0) { const KDbQueryColumnInfo::Vector fe(query.fieldsExpanded()); fieldsExpandedCount = fe.size(); dbg.nospace() << fieldsExpandedCount << "):\n"; first = true; for (int i = 0; i < fieldsExpandedCount; i++) { KDbQueryColumnInfo *ci = fe[i]; if (first) first = false; else dbg.nospace() << ",\n"; dbg.nospace() << *ci; } dbg.nospace() << '\n'; } //it's safer to delete fieldsExpanded for now // (debugString() could be called before all fields are added) //bindings dbg.nospace() << " - BINDINGS:\n"; bool bindingsExist = false; for (int i = 0; i < query.fieldCount(); i++) { const int tablePos = query.tableBoundToColumn(i); if (tablePos >= 0) { const QString tAlias(query.tableAlias(tablePos)); if (!tAlias.isEmpty()) { bindingsExist = true; dbg.space() << "FIELD"; dbg.space() << static_cast(query).field(i)->name(); dbg.space() << "USES ALIAS"; dbg.space() << tAlias; dbg.space() << "OF TABLE"; dbg.space() << query.tables()->at(tablePos)->name() << '\n'; } } } if (!bindingsExist) { dbg.nospace() << "\n"; } //tables dbg.nospace() << " - TABLES:\n"; first = true; foreach(KDbTableSchema *table, *query.tables()) { if (first) first = false; else dbg.nospace() << ","; dbg.space() << table->name(); } if (query.tables()->isEmpty()) dbg.nospace() << ""; //aliases dbg.nospace() << "\n - COLUMN ALIASES:\n"; if (query.columnAliasesCount() == 0) { dbg.nospace() << "\n"; } else { int i = -1; foreach(KDbField *f, *query.fields()) { i++; const QString alias(query.columnAlias(i)); if (!alias.isEmpty()) { dbg.nospace() << QString::fromLatin1("FIELD #%1:").arg(i); dbg.space() << (f->name().isEmpty() ? QLatin1String("") : f->name()) << " -> " << alias << '\n'; } } } dbg.nospace() << "\n- TABLE ALIASES:\n"; if (query.tableAliasesCount() == 0) { dbg.nospace() << "\n"; } else { int i = -1; foreach(KDbTableSchema* table, *query.tables()) { i++; const QString alias(query.tableAlias(i)); if (!alias.isEmpty()) { dbg.nospace() << QString::fromLatin1("table #%1:").arg(i); dbg.space() << (table->name().isEmpty() ? QLatin1String("") : table->name()) << " -> " << alias << '\n'; } } } if (!query.whereExpression().isNull()) { dbg.nospace() << " - WHERE EXPRESSION:\n" << query.whereExpression() << '\n'; } if (!query.orderByColumnList()->isEmpty()) { dbg.space() << QString::fromLatin1(" - ORDER BY (%1):\n").arg(query.orderByColumnList()->count()); dbg.nospace() << *query.orderByColumnList(); } return dbg.nospace(); } KDbTableSchema* KDbQuerySchema::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) QString tableNameLower; int num = -1; foreach(KDbTableSchema *table, d->tables) { num++; if (!tableNameLower.isEmpty() && table->name().toLower() != tableNameLower) { //two or more different tables return 0; } tableNameLower = tableAlias(num); } return d->tables.first(); } void KDbQuerySchema::setMasterTable(KDbTableSchema *table) { if (table) d->masterTable = table; } QList* KDbQuerySchema::tables() const { return &d->tables; } void KDbQuerySchema::addTable(KDbTableSchema *table, const QString& alias) { kdbDebug() << (void *)table << "alias=" << alias; if (!table) return; // only append table if: it has alias or it has no alias but there is no such table on the list if (alias.isEmpty() && d->tables.contains(table)) { int num = -1; foreach(KDbTableSchema *t, d->tables) { num++; if (0 == t->name().compare(table->name(), Qt::CaseInsensitive)) { if (tableAlias(num).isEmpty()) { kdbDebug() << "table" << table->name() << "without alias already added"; return; } } } } d->tables.append(table); if (!alias.isEmpty()) setTableAlias(d->tables.count() - 1, alias); } void KDbQuerySchema::removeTable(KDbTableSchema *table) { if (!table) return; if (d->masterTable == table) d->masterTable = 0; d->tables.removeAt(d->tables.indexOf(table)); //! @todo remove fields! } KDbTableSchema* KDbQuerySchema::table(const QString& tableName) const { //! @todo maybe use tables_byname? foreach(KDbTableSchema *table, d->tables) { if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { return table; } } return 0; } bool KDbQuerySchema::contains(KDbTableSchema *table) const { return d->tables.contains(table); } KDbField* KDbQuerySchema::findTableField(const QString &tableOrTableAndFieldName) const { QString tableName, fieldName; if (!KDb::splitToTableAndFieldParts(tableOrTableAndFieldName, &tableName, &fieldName, KDb::SetFieldNameIfNoTableName)) { return 0; } if (tableName.isEmpty()) { foreach(KDbTableSchema *table, d->tables) { if (table->field(fieldName)) return table->field(fieldName); } return 0; } KDbTableSchema *tableSchema = table(tableName); if (!tableSchema) return 0; return tableSchema->field(fieldName); } int KDbQuerySchema::columnAliasesCount() const { return d->columnAliasesCount(); } QString KDbQuerySchema::columnAlias(int position) const { return d->columnAlias(position); } bool KDbQuerySchema::hasColumnAlias(int position) const { return d->hasColumnAlias(position); } void KDbQuerySchema::setColumnAlias(int position, const QString& alias) { if (position >= m_fields.count()) { kdbWarning() << "position" << position << "out of range!"; return; } const QString fixedAlias(alias.trimmed()); KDbField *f = KDbFieldList::field(position); if (f->captionOrName().isEmpty() && fixedAlias.isEmpty()) { kdbWarning() << "position" << position << "could not remove alias when no name is specified for expression column!"; return; } d->setColumnAlias(position, fixedAlias); } int KDbQuerySchema::tableAliasesCount() const { return d->tableAliases.count(); } QString KDbQuerySchema::tableAlias(int position) const { return d->tableAliases.value(position); } QString KDbQuerySchema::tableAlias(const QString& tableName) const { const int pos = tablePosition(tableName); if (pos == -1) { return QString(); } return d->tableAliases.value(pos); } QString KDbQuerySchema::tableAliasOrName(const QString& tableName) const { const int pos = tablePosition(tableName); if (pos == -1) { return QString(); } return KDb::iifNotEmpty(d->tableAliases.value(pos), tableName); } int KDbQuerySchema::tablePositionForAlias(const QString& name) const { return d->tablePositionForAlias(name); } int KDbQuerySchema::tablePosition(const QString& tableName) const { int num = -1; foreach(KDbTableSchema* table, d->tables) { num++; if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { return num; } } return -1; } QList KDbQuerySchema::tablePositions(const QString& tableName) const { QList result; int num = -1; foreach(KDbTableSchema* table, d->tables) { num++; if (0 == table->name().compare(tableName, Qt::CaseInsensitive)) { result += num; } } return result; } bool KDbQuerySchema::hasTableAlias(int position) const { return d->tableAliases.contains(position); } int KDbQuerySchema::columnPositionForAlias(const QString& name) const { return d->columnPositionForAlias(name); } void KDbQuerySchema::setTableAlias(int position, const QString& alias) { if (position >= d->tables.count()) { kdbWarning() << "position" << position << "out of range!"; return; } const QString fixedAlias(alias.trimmed()); if (fixedAlias.isEmpty()) { const QString oldAlias(d->tableAliases.take(position)); if (!oldAlias.isEmpty()) { d->removeTablePositionForAlias(oldAlias); } } else { d->setTableAlias(position, fixedAlias); } } QList* KDbQuerySchema::relationships() const { return &d->relations; } KDbField::List* KDbQuerySchema::asterisks() const { return &d->asterisks; } KDbEscapedString KDbQuerySchema::statement() const { return d->sql; } void KDbQuerySchema::setStatement(const KDbEscapedString& sql) { d->sql = sql; } KDbField* KDbQuerySchema::field(const QString& name) { return field(name, true); } KDbField* KDbQuerySchema::field(const QString& identifier, bool expanded) const { KDbQueryColumnInfo *ci = columnInfo(identifier, expanded); return ci ? ci->field : 0; } KDbField* KDbQuerySchema::field(int id) { return KDbFieldList::field(id); } const KDbField* KDbQuerySchema::field(int id) const { return KDbFieldList::field(id); } KDbQueryColumnInfo* KDbQuerySchema::columnInfo(const QString& identifier, bool expanded) const { computeFieldsExpanded(); return expanded ? d->columnInfosByNameExpanded.value(identifier) : d->columnInfosByName.value(identifier); } KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpandedInternal( FieldsExpandedOptions options, bool onlyVisible) const { computeFieldsExpanded(); KDbQueryColumnInfo::Vector *realFieldsExpanded = onlyVisible ? d->visibleFieldsExpanded : d->fieldsExpanded; if (options == WithInternalFields || options == WithInternalFieldsAndRecordId) { //a ref to a proper pointer (as we cache the vector for two cases) KDbQueryColumnInfo::Vector*& tmpFieldsExpandedWithInternal = (options == WithInternalFields) ? (onlyVisible ? d->visibleFieldsExpandedWithInternal : d->fieldsExpandedWithInternal) : (onlyVisible ? d->visibleFieldsExpandedWithInternalAndRecordId : d->fieldsExpandedWithInternalAndRecordId); //special case if (!tmpFieldsExpandedWithInternal) { //glue expanded and internal fields and cache it const int internalFieldCount = d->internalFields ? d->internalFields->size() : 0; const int fieldsExpandedVectorSize = realFieldsExpanded->size(); const int size = fieldsExpandedVectorSize + internalFieldCount + ((options == WithInternalFieldsAndRecordId) ? 1 : 0) /*ROWID*/; tmpFieldsExpandedWithInternal = new KDbQueryColumnInfo::Vector(size); for (int i = 0; i < fieldsExpandedVectorSize; ++i) { (*tmpFieldsExpandedWithInternal)[i] = realFieldsExpanded->at(i); } if (internalFieldCount > 0) { for (int i = 0; i < internalFieldCount; ++i) { KDbQueryColumnInfo *info = d->internalFields->at(i); (*tmpFieldsExpandedWithInternal)[fieldsExpandedVectorSize + i] = info; } } if (options == WithInternalFieldsAndRecordId) { if (!d->fakeRecordIdField) { d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), KDbField::BigInteger); d->fakeRecordIdCol = new KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true); } (*tmpFieldsExpandedWithInternal)[fieldsExpandedVectorSize + internalFieldCount] = d->fakeRecordIdCol; } } return *tmpFieldsExpandedWithInternal; } if (options == Default) { return *realFieldsExpanded; } //options == Unique: QSet columnsAlreadyFound; const int fieldsExpandedCount(realFieldsExpanded->count()); KDbQueryColumnInfo::Vector result(fieldsExpandedCount); //initial size is set //compute unique list int uniqueListCount = 0; for (int i = 0; i < fieldsExpandedCount; i++) { KDbQueryColumnInfo *ci = realFieldsExpanded->at(i); if (!columnsAlreadyFound.contains(ci->aliasOrName())) { columnsAlreadyFound.insert(ci->aliasOrName()); result[uniqueListCount++] = ci; } } result.resize(uniqueListCount); //update result size return result; } KDbQueryColumnInfo::Vector KDbQuerySchema::internalFields() const { computeFieldsExpanded(); return d->internalFields ? *d->internalFields : KDbQueryColumnInfo::Vector(); } KDbQueryColumnInfo* KDbQuerySchema::expandedOrInternalField(int index) const { return fieldsExpanded(WithInternalFields).value(index); } inline static QString lookupColumnKey(KDbField *foreignField, KDbField* field) { QString res; if (field->table()) // can be 0 for anonymous fields built as joined multiple visible columns res = field->table()->name() + QLatin1Char('.'); return res + field->name() + QLatin1Char('_') + foreignField->table()->name() + QLatin1Char('.') + foreignField->name(); } void KDbQuerySchema::computeFieldsExpanded() const { if (d->fieldsExpanded) return; if (!d->columnsOrder) { d->columnsOrder = new QHash(); d->columnsOrderWithoutAsterisks = new QHash(); } 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) KDbQueryColumnInfo::List list; //temporary KDbQueryColumnInfo::List lookup_list; //temporary, for collecting additional fields related to lookup fields QHash columnInfosOutsideAsterisks; //helper for filling d->columnInfosByName int i = 0; int numberOfColumnsWithMultipleVisibleFields = 0; //used to find an unique name for anonymous field int fieldPosition = -1; foreach(KDbField *f, m_fields) { fieldPosition++; if (f->isQueryAsterisk()) { if (static_cast(f)->isSingleTableAsterisk()) { const KDbField::List *ast_fields = static_cast(f)->table()->fields(); foreach(KDbField *ast_f, *ast_fields) { KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); } } else {//all-tables asterisk: iterate through table list foreach(KDbTableSchema *table, d->tables) { //add all fields from this table const KDbField::List *tab_fields = table->fields(); foreach(KDbField *tab_f, *tab_fields) { //! @todo (js): perhaps not all fields should be appended here // d->detailedVisibility += isFieldVisible(fieldPosition); // list.append(tab_f); KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); } } } } else { //a single field KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition)); list.append(ci); columnInfosOutsideAsterisks.insert(ci, true); kdbDebug() << "caching (unexpanded) column's order:" << *ci << "at position" << fieldPosition; d->columnsOrder->insert(ci, fieldPosition); d->columnsOrderWithoutAsterisks->insert(ci, fieldPosition); //handle lookup field schema KDbLookupFieldSchema *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" KDbLookupFieldSchema::RecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchema::RecordSource::Table) { KDbTableSchema *lookupTable = connection()->tableSchema(recordSource.name()); KDbFieldList* visibleColumns = 0; KDbField *boundField = 0; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns())) && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) { KDbField *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 KDbField(); visibleColumn->setName( QString::fromLatin1("[multiple_visible_fields_%1]") .arg(++numberOfColumnsWithMultipleVisibleFields)); visibleColumn->setExpression( KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); if (!d->ownedVisibleColumns) { d->ownedVisibleColumns = new KDbField::List(); } d->ownedVisibleColumns->append(visibleColumn); // remember to delete later } lookup_list.append( new KDbQueryColumnInfo(visibleColumn, QString(), 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 (recordSource.type() == KDbLookupFieldSchema::RecordSource::Query) { KDbQuerySchema *lookupQuery = connection()->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(lookupQuery->fieldsExpanded()); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; KDbQueryColumnInfo *boundColumnInfo = 0; if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn()))) continue; KDbField *boundField = boundColumnInfo->field; if (!boundField) continue; const QList visibleColumns(lookupFieldSchema->visibleColumns()); bool ok = true; // all indices in visibleColumns should be in [0..lookupQueryFieldsExpanded.size()-1] foreach(int visibleColumn, visibleColumns) { if (visibleColumn >= lookupQueryFieldsExpanded.count()) { ok = false; break; } } if (!ok) continue; KDbField *visibleColumn = 0; // for single visible column, just add it as-is if (visibleColumns.count() == 1) { visibleColumn = lookupQueryFieldsExpanded.value(visibleColumns.first())->field; } else { // for multiple visible columns, build an expression column // (the expression object will be owned by column info) visibleColumn = new KDbField(); visibleColumn->setName( QString::fromLatin1("[multiple_visible_fields_%1]") .arg(++numberOfColumnsWithMultipleVisibleFields)); visibleColumn->setExpression( KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, QVariant()/*not important*/)); if (!d->ownedVisibleColumns) { d->ownedVisibleColumns = new KDbField::List(); } d->ownedVisibleColumns->append(visibleColumn); // remember to delete later } lookup_list.append( new KDbQueryColumnInfo(visibleColumn, QString(), 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 KDbQueryColumnInfo::Vector(list.count()); d->visibleFieldsExpanded = new KDbQueryColumnInfo::Vector(list.count()); d->columnsOrderExpanded = new QHash(); } else {//for future: qDeleteAll(*d->fieldsExpanded); d->fieldsExpanded->clear(); d->fieldsExpanded->resize(list.count()); d->visibleFieldsExpanded->clear(); d->visibleFieldsExpanded->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 = -1; int visibleIndex = -1; foreach(KDbQueryColumnInfo* ci, list) { i++; (*d->fieldsExpanded)[i] = ci; if (ci->visible) { ++visibleIndex; (*d->visibleFieldsExpanded)[visibleIndex] = 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.contains(ci->alias)) { d->columnInfosByNameExpanded.insert(ci->alias, ci); } QString tableAndAlias(ci->alias); if (ci->field->table()) tableAndAlias.prepend(ci->field->table()->name() + QLatin1Char('.')); if (!d->columnInfosByNameExpanded.contains(tableAndAlias)) { d->columnInfosByNameExpanded.insert(tableAndAlias, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!d->columnInfosByName.contains(ci->alias)) { d->columnInfosByName.insert(ci->alias, ci); } if (!d->columnInfosByName.contains(tableAndAlias)) { d->columnInfosByName.insert(tableAndAlias, ci); } } } else { //no alias: store name and table.name if (!d->columnInfosByNameExpanded.contains(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() + QLatin1Char('.')); if (!d->columnInfosByNameExpanded.contains(tableAndName)) { d->columnInfosByNameExpanded.insert(tableAndName, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!d->columnInfosByName.contains(ci->field->name())) { d->columnInfosByName.insert(ci->field->name(), ci); } if (!d->columnInfosByName.contains(tableAndName)) { d->columnInfosByName.insert(tableAndName, ci); } } } } d->visibleFieldsExpanded->resize(visibleIndex + 1); //remove duplicates for lookup fields QHash lookup_dict; //used to fight duplicates and to update KDbQueryColumnInfo::indexForVisibleLookupValue() // (a mapping from table.name string to int* lookupFieldIndex i = 0; for (QMutableListIterator it(lookup_list); it.hasNext();) { KDbQueryColumnInfo* ci = it.next(); const QString key(lookupColumnKey(ci->foreignColumn()->field, ci->field)); if (lookup_dict.contains(key)) { // this table.field is already fetched by this query it.remove(); delete ci; } else { lookup_dict.insert(key, i); i++; } } //create internal expanded list with lookup fields if (d->internalFields) { qDeleteAll(*d->internalFields); d->internalFields->clear(); d->internalFields->resize(lookup_list.count()); } delete d->fieldsExpandedWithInternal; //clear cache delete d->fieldsExpandedWithInternalAndRecordId; //clear cache d->fieldsExpandedWithInternal = 0; d->fieldsExpandedWithInternalAndRecordId = 0; if (!lookup_list.isEmpty() && !d->internalFields) {//create on demand d->internalFields = new KDbQueryColumnInfo::Vector(lookup_list.count()); } i = -1; foreach(KDbQueryColumnInfo *ci, lookup_list) { i++; //add it to the internal list (*d->internalFields)[i] = ci; d->columnsOrderExpanded->insert(ci, list.count() + i); } //update KDbQueryColumnInfo::indexForVisibleLookupValue() cache for columns numberOfColumnsWithMultipleVisibleFields = 0; for (i = 0; i < d->fieldsExpanded->size(); i++) { KDbQueryColumnInfo* ci = d->fieldsExpanded->at(i); //! @todo KDbQuerySchema itself will also support lookup fields... KDbLookupFieldSchema *lookupFieldSchema = ci->field->table() ? ci->field->table()->lookupFieldSchema(*ci->field) : 0; if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0) continue; const KDbLookupFieldSchema::RecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchema::RecordSource::Table) { KDbTableSchema *lookupTable = connection()->tableSchema(recordSource.name()); KDbFieldList* visibleColumns = 0; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns()))) { // for single visible column, just add it as-is if (visibleColumns->fieldCount() == 1) { KDbField *visibleColumn = visibleColumns->fields()->first(); const QString key(lookupColumnKey(ci->field, visibleColumn)); int index = lookup_dict.value(key, -99); if (index != -99) 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(), ci->field->name())); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchema::RecordSource::Query) { KDbQuerySchema *lookupQuery = connection()->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded(lookupQuery->fieldsExpanded()); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; KDbQueryColumnInfo *boundColumnInfo = 0; if (!(boundColumnInfo = lookupQueryFieldsExpanded.value(lookupFieldSchema->boundColumn()))) continue; KDbField *boundField = boundColumnInfo->field; if (!boundField) continue; const QList visibleColumns(lookupFieldSchema->visibleColumns()); // for single visible column, just add it as-is if (visibleColumns.count() == 1) { if (lookupQueryFieldsExpanded.count() > visibleColumns.first()) { // sanity check KDbField *visibleColumn = lookupQueryFieldsExpanded.at(visibleColumns.first())->field; const QString key(lookupColumnKey(ci->field, visibleColumn)); int index = lookup_dict.value(key, -99); if (index != -99) 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(), ci->field->name())); int index = lookup_dict.value(key, -99); if (index != -99) ci->setIndexForVisibleLookupValue(d->fieldsExpanded->size() + index); } } else { kdbWarning() << "unsupported record source type" << recordSource.typeName(); } } } QHash KDbQuerySchema::columnsOrder(ColumnsOrderOptions options) const { if (!d->columnsOrder) computeFieldsExpanded(); if (options == UnexpandedList) return *d->columnsOrder; else if (options == UnexpandedListWithoutAsterisks) return *d->columnsOrderWithoutAsterisks; return *d->columnsOrderExpanded; } QVector KDbQuerySchema::pkeyFieldsOrder() const { if (d->pkeyFieldsOrder) return *d->pkeyFieldsOrder; KDbTableSchema *tbl = masterTable(); if (!tbl || !tbl->primaryKey()) return QVector(); //get order of PKEY fields (e.g. for records updating or inserting ) KDbIndexSchema *pkey = tbl->primaryKey(); kdbDebug() << *pkey; d->pkeyFieldsOrder = new QVector(pkey->fieldCount(), -1); const int fCount = fieldsExpanded().count(); d->pkeyFieldCount = 0; for (int i = 0; i < fCount; i++) { KDbQueryColumnInfo *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 */) { kdbDebug() << "FIELD" << fi->field->name() << "IS IN PKEY AT POSITION #" << fieldIndex; (*d->pkeyFieldsOrder)[fieldIndex] = i; d->pkeyFieldCount++; } } kdbDebug() << d->pkeyFieldCount << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name(); return *d->pkeyFieldsOrder; } int KDbQuerySchema::pkeyFieldCount() { (void)pkeyFieldsOrder(); /* rebuild information */ return d->pkeyFieldCount; } KDbRelationship* KDbQuerySchema::addRelationship(KDbField *field1, KDbField *field2) { //@todo: find existing global db relationships KDbRelationship *r = new KDbRelationship(this, field1, field2); if (r->isEmpty()) { delete r; return 0; } d->relations.append(r); return r; } KDbQueryColumnInfo::List* KDbQuerySchema::autoIncrementFields() const { if (!d->autoincFields) { d->autoincFields = new KDbQueryColumnInfo::List(); } KDbTableSchema *mt = masterTable(); if (!mt) { kdbWarning() << "no master table!"; return d->autoincFields; } if (d->autoincFields->isEmpty()) {//no cache KDbQueryColumnInfo::Vector fexp = fieldsExpanded(); for (int i = 0; i < fexp.count(); i++) { KDbQueryColumnInfo *fi = fexp[i]; if (fi->field->table() == mt && fi->field->isAutoIncrement()) { d->autoincFields->append(fi); } } } return d->autoincFields; } // static KDbEscapedString KDbQuerySchema::sqlColumnsList(const KDbQueryColumnInfo::List& infolist, KDbConnection *conn, KDb::IdentifierEscapingType escapingType) { KDbEscapedString result; result.reserve(256); bool start = true; foreach(KDbQueryColumnInfo* ci, infolist) { if (!start) result += ","; else start = false; result += escapeIdentifier(ci->field->name(), conn, escapingType); } return result; } KDbEscapedString KDbQuerySchema::autoIncrementSQLFieldsList(KDbConnection *conn) const { // QWeakPointer driverWeakPointer // = DriverManagerInternal::self()->driverWeakPointer(*conn->driver()); if ( /*d->lastUsedDriverForAutoIncrementSQLFieldsList != driverWeakPointer ||*/ d->autoIncrementSQLFieldsList.isEmpty()) { d->autoIncrementSQLFieldsList = KDbQuerySchema::sqlColumnsList(*autoIncrementFields(), conn); //d->lastUsedDriverForAutoIncrementSQLFieldsList = driverWeakPointer; } return d->autoIncrementSQLFieldsList; } -void KDbQuerySchema::setWhereExpression(const KDbExpression& expr) +static void setResult(const KDbParseInfoInternal &parseInfo, + QString *errorMessage, QString *errorDescription) { - d->whereExpr = expr.clone(); + if (errorMessage) { + *errorMessage = parseInfo.errorMessage(); + } + if (errorDescription) { + *errorDescription = parseInfo.errorDescription(); + } +} + +bool KDbQuerySchema::setWhereExpression(const KDbExpression &expr, QString *errorMessage, + QString *errorDescription) +{ + KDbExpression newWhereExpr = expr.clone(); + KDbParseInfoInternal parseInfo(this); + QString tempErrorMessage; + QString tempErrorDescription; + QString *errorMessagePointer = errorMessage ? errorMessage : &tempErrorMessage; + QString *errorDescriptionPointer + = errorDescription ? errorDescription : &tempErrorDescription; + if (!newWhereExpr.validate(&parseInfo)) { + setResult(parseInfo, errorMessagePointer, errorDescription); + kdbWarning() << "message=" << *errorMessagePointer + << "description=" << *errorDescriptionPointer; + kdbWarning() << newWhereExpr; + d->whereExpr = KDbExpression(); + return false; + } + errorMessagePointer->clear(); + errorDescriptionPointer->clear(); + Private::setWhereExpressionInternal(this, newWhereExpr); + return true; } -void KDbQuerySchema::addToWhereExpression(KDbField *field, - const QVariant& value, KDbToken relation) +bool KDbQuerySchema::addToWhereExpression(KDbField *field, const QVariant &value, + KDbToken relation, QString *errorMessage, + QString *errorDescription) { KDbToken token; if (value.isNull()) { token = KDbToken::SQL_NULL; } else { const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive if (KDbField::isIntegerType(type)) { token = KDbToken::INTEGER_CONST; } else if (KDbField::isFPNumericType(type)) { token = KDbToken::REAL_CONST; } else { token = KDbToken::CHARACTER_STRING_LITERAL; } //! @todo date, time } KDbBinaryExpression newExpr( KDbConstExpression(token, value), relation, KDbVariableExpression((field->table() ? (field->table()->name() + QLatin1Char('.')) : QString()) + field->name()) ); - if (d->whereExpr.isNull()) { - d->whereExpr = newExpr; - } - else { - d->whereExpr = KDbBinaryExpression( + const KDbExpression origWhereExpr = d->whereExpr; + if (!d->whereExpr.isNull()) { + newExpr = KDbBinaryExpression( d->whereExpr, KDbToken::AND, newExpr ); } + const bool result = setWhereExpression(newExpr, errorMessage, errorDescription); + if (!result) { // revert, setWhereExpression() cleared it + d->whereExpr = origWhereExpr; + } + return result; } /* void KDbQuerySchema::addToWhereExpression(KDbField *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 */ KDbExpression KDbQuerySchema::whereExpression() const { return d->whereExpr; } void KDbQuerySchema::setOrderByColumnList(const KDbOrderByColumnList& list) { delete d->orderByColumnList; d->orderByColumnList = new KDbOrderByColumnList(list, 0, 0); // all field names should be found, exit otherwise ..........? } KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() { return d->orderByColumnList; } const KDbOrderByColumnList* KDbQuerySchema::orderByColumnList() const { return d->orderByColumnList; } QList KDbQuerySchema::parameters() const { if (whereExpression().isNull()) return QList(); QList params; whereExpression().getQueryParameters(¶ms); return params; } -static void setResult(const KDbParseInfoInternal &parseInfo, - QString *errorMessage, QString *errorDescription) -{ - if (errorMessage) { - *errorMessage = parseInfo.errorMessage(); - } - if (errorDescription) { - *errorDescription = parseInfo.errorDescription(); - } -} - bool KDbQuerySchema::validate(QString *errorMessage, QString *errorDescription) { KDbParseInfoInternal parseInfo(this); foreach(KDbField* f, *fields()) { if (f->isExpression()) { if (!f->expression().validate(&parseInfo)) { setResult(parseInfo, errorMessage, errorDescription); return false; } } } if (!whereExpression().validate(&parseInfo)) { setResult(parseInfo, errorMessage, errorDescription); return false; } return true; } //--------------------------------------------------- KDbQueryAsterisk::KDbQueryAsterisk(KDbQuerySchema *query, KDbTableSchema *table) : KDbField() , m_table(table) { Q_ASSERT(query); m_parent = query; setType(KDbField::Asterisk); } KDbQueryAsterisk::KDbQueryAsterisk(const KDbQueryAsterisk &asterisk) : KDbField(asterisk) , m_table(asterisk.table()) { } KDbQueryAsterisk::~KDbQueryAsterisk() { } KDbQuerySchema *KDbQueryAsterisk::query() const { return static_cast(m_parent); } KDbTableSchema* KDbQueryAsterisk::table() const { return m_table; } KDbField* KDbQueryAsterisk::copy() const { return new KDbQueryAsterisk(*this); } void KDbQueryAsterisk::setTable(KDbTableSchema *table) { m_table = table; } bool KDbQueryAsterisk::isSingleTableAsterisk() const { return m_table; } bool KDbQueryAsterisk::isAllTableAsterisk() const { return !m_table; } QDebug operator<<(QDebug dbg, const KDbQueryAsterisk& asterisk) { if (asterisk.isAllTableAsterisk()) { dbg.nospace() << "ALL-TABLES ASTERISK (*) ON TABLES("; bool first = true; foreach(KDbTableSchema *table, *asterisk.query()->tables()) { if (first) first = false; else dbg.nospace() << ','; dbg.space() << table->name(); } dbg.space() << ')'; } else { dbg.nospace() << "SINGLE-TABLE ASTERISK (" << asterisk.table()->name() << ".*)"; } return dbg.space(); } diff --git a/src/KDbQuerySchema.h b/src/KDbQuerySchema.h index 1d6391b7..df011ffb 100644 --- a/src/KDbQuerySchema.h +++ b/src/KDbQuerySchema.h @@ -1,874 +1,898 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2017 Jarosław Staniek 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. */ #ifndef KDB_QUERYSCHEMA_H #define KDB_QUERYSCHEMA_H #include #include #include "KDbFieldList.h" #include "KDbObject.h" #include "KDbQueryColumnInfo.h" #include "KDbToken.h" class KDbConnection; class KDbTableSchema; class KDbRelationship; class KDbQueryAsterisk; class KDbQuerySchemaParameter; //! @short KDbOrderByColumn provides information about a single query column used for sorting /*! The column can be expression or table field. */ class KDB_EXPORT KDbOrderByColumn { public: typedef QList::ConstIterator ListConstIterator; //! Creates an empty information about a single query column. KDbOrderByColumn(); //! Creates information about a single query column @a column used for sorting. //! @a column must not be 0. explicit KDbOrderByColumn(KDbQueryColumnInfo* column, bool ascending = true, int pos = -1); //! Like above but used when the field @a field is not present on the list of columns. //! (e.g. SELECT a FROM t ORDER BY b; where T is a table with fields (a,b)). //! @a field must not be 0. explicit KDbOrderByColumn(KDbField* field, bool ascending = true); ~KDbOrderByColumn(); /*! @return copy of this KDbOrderByColumn object. In @a fromQuery and @a toQuery is needed if column() is assigned to this info. Then, column info within @a toQuery will be assigned to the new KDbOrderByColumn object, corresponding to column() from "this" KDbOrderByColumn object. */ KDbOrderByColumn* copy(KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) const; //! A column to sort. KDbQueryColumnInfo* column() const; /*! A helper for column() that allows you to know that sorting column was defined by providing its position. -1 by default. Example query: SELECT a, b FROM T ORDER BY 2 */ int position() const; //! A field to sort, used only in case when the second constructor was used. KDbField *field() const; //! @return true if ascending sorting should be performed (the default). bool ascending() const; //! @return true if this column is thesame as @a col bool operator== (const KDbOrderByColumn& col) const; /*! @return a string like "name ASC" usable for building an SQL statement. If @a includeTableNames is true (the default) field is output in a form of "tablename.fieldname" (but only if fieldname is not a name of alias). @a escapingType can be used to alter default escaping type. If @a conn is not provided for DriverEscaping, no escaping is performed. */ KDbEscapedString toSQLString(bool includeTableName = true, KDbConnection *conn = 0, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; protected: //! Column to sort KDbQueryColumnInfo* m_column; //!< 0 if m_field is non-0. int m_pos; //!< A helper for m_column that allows to know that sorting column //!< was defined by providing its position. -1 by default. //!< e.g. SELECT a, b FROM T ORDER BY 2 KDbField* m_field; //!< Used only in case when the second contructor is used. bool m_ascending; //!< true if ascending sorting should be performed (the default). private: Q_DISABLE_COPY(KDbOrderByColumn) }; //! @short KDbOrderByColumnList provides list of sorted columns for a query schema class KDB_EXPORT KDbOrderByColumnList : protected QList { public: /*! Constructs empty list of ordered columns. */ KDbOrderByColumnList(); /*! A copy constructor. */ KDbOrderByColumnList(const KDbOrderByColumnList& other, KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery); ~KDbOrderByColumnList(); class KDB_EXPORT const_iterator : public QList::const_iterator { public: inline const_iterator() : QList::const_iterator() {} inline const_iterator(const QList::const_iterator &o) : QList::const_iterator(o) {} }; class KDB_EXPORT iterator : public QList::iterator { public: inline iterator() : QList::iterator() {} inline iterator(const QList::iterator &o) : QList::iterator(o) {} }; /*! Appends multiple fields for sorting. @a querySchema is used to find appropriate field or alias name. @return false if there is at least one name for which a field or alias name does not exist (all the newly appended fields are removed in this case) */ //! @note @a querySchema must not be 0. bool appendFields(KDbQuerySchema* querySchema, const QString& field1, bool ascending1 = true, const QString& field2 = QString(), bool ascending2 = true, const QString& field3 = QString(), bool ascending3 = true, const QString& field4 = QString(), bool ascending4 = true, const QString& field5 = QString(), bool ascending5 = true); /*! Appends column @a columnInfo. Ascending sorting is set is @a ascending is true. */ //! @note @a columnInfo must not be 0. void appendColumn(KDbQueryColumnInfo* columnInfo, bool ascending = true); /*! Appends a field @a field. Ascending sorting is set is @a ascending is true. Read documentation of @ref KDbOrderByColumn(const KDbField& field, bool ascending = true) for more info. */ //! @note @a field must not be 0. void appendField(KDbField* field, bool ascending = true); /*! Appends field with a name @a field. Ascending sorting is set is @a ascending is true. @return true on successful appending, and false if there is no such field or alias name in the @a querySchema. */ //! @note @a querySchema must not be 0. bool appendField(KDbQuerySchema* querySchema, const QString& fieldName, bool ascending = true); /*! Appends a column that is at position @a pos (counted from 0). @return true on successful adding and false if there is no such position @a pos. */ //! @note @a querySchema must not be 0. bool appendColumn(KDbQuerySchema* querySchema, bool ascending = true, int pos = -1); /*! @return true if the list is empty. */ bool isEmpty() const; /*! @return number of elements of the list. */ int count() const; /*! Removes all elements from the list (deletes them). */ void clear(); iterator begin(); iterator end(); const_iterator constBegin() const; const_iterator constEnd() const; /*! @return a string like "name ASC, 2 DESC" usable for building an SQL statement. If @a includeTableNames is true (the default) fields are output in a form of "tablename.fieldname". @a escapingType can be used to alter default escaping type. If @a conn is not provided for DriverEscaping, no escaping is performed. */ KDbEscapedString toSQLString(bool includeTableNames = true, KDbConnection *conn = 0, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; private: Q_DISABLE_COPY(KDbOrderByColumnList) }; //! @short KDbQuerySchema provides information about database query /*! The query that can be executed using KDb-compatible SQL database engine or used as an introspection tool. KDb parser builds KDbQuerySchema objects by parsing SQL statements. */ class KDB_EXPORT KDbQuerySchema : public KDbFieldList, public KDbObject { public: /*! Creates empty query object (without columns). */ KDbQuerySchema(); /*! Creates query schema object that is equivalent to "SELECT * FROM table" sql command. Schema of @a table is used to contruct this query -- it is defined by just adding all the fields to the query in natural order. To avoid problems (e.g. with fields added outside of Kexi using ALTER TABLE) we do not use "all-tables query asterisk" (see KDbQueryAsterisk) item to achieve this effect. Properties such as the name and caption of the query are inherited from table schema. We consider that query schema based on @a table is not (a least yet) stored in a system table, so query connection is set to NULL (even if @a tableSchema's connection is not NULL). Id of the created query is set to 0. */ explicit KDbQuerySchema(KDbTableSchema *tableSchema); /*! Copy constructor. Creates deep copy of @a querySchema. KDbQueryAsterisk objects are deeply copied while only pointers to KDbField objects are copied. */ KDbQuerySchema(const KDbQuerySchema& querySchema); virtual ~KDbQuerySchema(); /*! Inserts @a field to the columns list at @a position. Inserted field will not be owned by this KDbQuerySchema object, but still by corresponding KDbTableSchema. As @a field object you can also pass KDbQueryAsterisk, (see KDbQueryAsterisk class description). Note: After inserting a field, corresponding table will be automatically added to query's tables list if it is not present there (see tables()). KDbField must have its table assigned. Added field will be visible. Use insertField(position, field, false) to add invisible field. */ virtual bool insertField(int position, KDbField *field); /* Like above method, but you can also set column's visibility. New column is not bound explicitly to any table. */ bool insertField(int position, KDbField *field, bool visible); /* Like above method, but the new column can also be explicitly bound to a specific position on tables list. If @a visible is true (the default), the field will be visible. If bindToTable==-1, no particular table should be bound. @see tableBoundToColumn(int columnPosition) */ bool insertField(int position, KDbField *field, int bindToTable, bool visible = true); /*! Adds @a field to the columns list. If @a visible is true (the default), the field will be visible. @see insertField() */ bool addField(KDbField* field, bool visible = true); /*! Adds @a field to the columns list. Also binds to a table at @a bindToTable position. Use bindToTable==-1 if no table should be bound. If @a visible is true (the default), the field will be visible. @see insertField() @see tableBoundToColumn(int columnPosition) */ bool addField(KDbField* field, int bindToTable, bool visible = true); /*! Removes field from the columns list. Use with care. */ virtual bool removeField(KDbField *field); /*! Adds a field built on top of @a expr expression. This creates a new KDbField object and adds it to the query schema using addField(). @a expr will be owned by the query object. */ bool addExpression(const KDbExpression& expr, bool visible = true); /*! @return visibility flag for column at @a position. By default column is visible. */ bool isColumnVisible(int position) const; //! Sets visibility flag for column at @a position to @a v. void setColumnVisible(int position, bool v); /*! Adds @a asterisk at the and of columns list. */ bool addAsterisk(KDbQueryAsterisk *asterisk, bool visible = true); /*! Removes all columns and their aliases from the columns list, removes all tables and their aliases from the tables list within this query. Sets master table information to NULL. Does not destroy any objects though. Clears name and all other properties. @see KDbFieldList::clear() */ virtual void clear(); /*! If query was created using a connection, returns this connection object, otherwise NULL. */ KDbConnection* connection() const; /*! @return table that is master to this query. All potentially-editable columns within this query belong just to this table. This method also can return NULL if there are no tables at all, or if previously assigned master table schema has been removed with removeTable(). Every query that has at least one table defined, should have assigned a master table. If no master table is assigned explicitly, but only one table used in this query, a single table is returned here, even if there are table aliases, (e.g. "T" table is returned for "SELECT T1.A, T2.B FROM T T1, T T2" statement). */ KDbTableSchema* masterTable() const; /*! Sets master table of this query to @a table. This table should be also added to query's tables list using addTable(). If @a table equals NULL, nothing is performed. @see masterTable() */ void setMasterTable(KDbTableSchema *table); /*! @return list of tables used in a query. This also includes master table. @see masterTable() */ QList* tables() const; /*! Adds @a table schema as one of tables used in a query. If @a alias is not empty, it will be assigned to this table using setTableAlias(position, alias). */ void addTable(KDbTableSchema *table, const QString& alias = QString()); /*! Removes @a table schema from this query. This does not destroy @a table object but only takes it out of the list. If this table was master for the query, master table information is also invalidated. */ void removeTable(KDbTableSchema *table); /*! @return table with name @a tableName or 0 if this query has no such table. */ KDbTableSchema* table(const QString& tableName) const; /*! @return true if the query uses @a table. */ bool contains(KDbTableSchema *table) const; /*! Convenience function. @return table field by searching through all tables in this query. The field does not need to be included on the list of query columns. Similarly, query aliases are not taken into account. @a tableOrTableAndFieldName string may contain table name and field name with '.' character between them, e.g. "mytable.myfield". This is recommended way to avoid ambiguity. 0 is returned if the query has no such table defined of the table has no such field defined. If you do not provide a table name, the first field found is returned. KDbQuerySchema::table("mytable")->field("myfield") could be alternative for findTableField("mytable.myfield") but it can crash if "mytable" is not defined in the query. @see KDb::splitToTableAndFieldParts() */ KDbField* findTableField(const QString &tableOrTableAndFieldName) const; /*! @return alias of a column at @a position or null string If there is no alias for this column or if there is no such column within the query defined. If the column is an expression and has no alias defined, a new unique alias will be generated automatically on this call. */ QString columnAlias(int position) const; /*! @return number of column aliases */ int columnAliasesCount() const; /*! Provided for convenience. @return true if a column at @a position has non empty alias defined within the query. If there is no alias for this column, or if there is no such column in the query defined, false is returned. */ bool hasColumnAlias(int position) const; /*! Sets @a alias for a column at @a position, within the query. Passing empty string to @a alias clears alias for a given column. */ void setColumnAlias(int position, const QString& alias); /*! @return a table position (within FROM section), that is bound to column at @a columnPosition (within SELECT section). This information can be used to find if there is alias defined for a table that is referenced by a given column. For example, for "SELECT t2.id FROM table1 t1, table2 t2" query statement, columnBoundToTable(0) returns 1, what means that table at position 1 (within FROM section) is bound to column at position 0, so we can now call tableAlias(1) to see if we have used alias for this column (t2.id) or just a table name (table2.id). These checks are performed e.g. by KDbConnection::selectStatement() to construct a statement string maximally identical to originally defined query statement. -1 is returned if: - @a columnPosition is out of range (i.e. < 0 or >= fieldCount()) - a column at @a columnPosition is not bound to any table (i.e. no database field is used for this column, e.g. "1" constant for "SELECT 1 from table" query statement) */ int tableBoundToColumn(int columnPosition) const; /*! @return number of table aliases */ int tableAliasesCount() const; /*! @return alias of a table at @a position (within FROM section) or null string if there is no alias for this table or if there is no such table within the query defined. */ QString tableAlias(int position) const; /*! @return alias of a table @a tableName (within FROM section) or empty value if there is no alias for this table or if there is no such table within the query defined. */ QString tableAlias(const QString& tableName) const; /*! @return alias of a table @a tableName (within FROM section). If there is no alias for this table, its name is returned. Empty value is returned if there is no such table within the query defined. */ QString tableAliasOrName(const QString& tableName) const; /*! @return table position (within FROM section) that has attached alias @a name. If there is no such alias, -1 is returned. Only first table's position attached for this alias is returned. It is not especially bad, since aliases rarely can be duplicated, what leads to ambiguity. Duplicated aliases are only allowed for trivial queries that have no database fields used within their columns, e.g. "SELECT 1 from table1 t, table2 t" is ok but "SELECT t.id from table1 t, table2 t" is not. */ int tablePositionForAlias(const QString& name) const; /*! @return position (within the FROM section) of table @a tableName. -1 is returned if there's no such table declared in the FROM section. @see tablePositions() */ int tablePosition(const QString& tableName) const; /*! @return a list of all occurrences of table @a tableName (within the FROM section). E.g. for "SELECT * FROM table t, table t2" tablePositions("table") returns {0, 1} list. Empty list is returned if there's no table @a tableName used in the FROM section at all. @see tablePosition() */ QList tablePositions(const QString& tableName) const; /*! Provided for convenience. @return true if a table at @a position (within FROM section of the query) has non empty alias defined. If there is no alias for this table, or if there is no such table in the query defined, false is returned. */ bool hasTableAlias(int position) const; /*! @return column position that has defined alias @a name. If there is no such alias, -1 is returned. */ int columnPositionForAlias(const QString& name) const; /*! Sets @a alias for a table at @a position (within FROM section of the query). Passing empty sting to @a alias clears alias for a given table (only for specified @a position). */ void setTableAlias(int position, const QString& alias); /*! @return a list of relationships defined for this query */ QList* relationships() const; /*! Adds a new relationship defined by @a field1 and @a field2. Both fields should belong to two different tables of this query. This is convenience function useful for a typical cases. It automatically creates KDbRelationship object for this query. If one of the fields are primary keys, it will be detected and appropriate master-detail relation will be established. This functiuon does nothing if the arguments are invalid. */ KDbRelationship* addRelationship(KDbField *field1, KDbField *field2); /*! @return list of KDbQueryAsterisk objects defined for this query */ KDbField::List* asterisks() const; /*! @return field for @a identifier or 0 if no field for this name was found within the query. fieldsExpanded() method is used to lookup expanded list of the query fields, so queries with asterisks are processed well. If a field has alias defined, name is not taken into account, but only its alias. If a field has no alias: - field's name is checked - field's table and field's name are checked in a form of "tablename.fieldname", so you can provide @a identifier in this form to avoid ambiguity. If there are more than one fields with the same name equal to @a identifier, first-found is returned (checking is performed from first to last query field). Structures needed to compute result of this method are cached, so only first usage costs o(n) - another usages cost o(1). Example: Let query be defined by "SELECT T.B AS X, T.* FROM T" statement and let T be table containing fields A, B, C. Expanded list of columns for the query is: T.B AS X, T.A, T.B, T.C. - Calling field("B") will return a pointer to third query column (not the first, because it is covered by "X" alias). Additionally, calling field("X") will return the same pointer. - Calling field("T.A") will return the same pointer as field("A"). */ virtual KDbField* field(const QString& name, bool expanded) const; /*! This is overloaded method KDbField* field(const QString& name, bool expanded) with expanded = true. This method is also a product of inheritance from KDbFieldList. */ virtual KDbField* field(const QString& name); /*! @return field id or NULL if there is no such a field. */ KDbField* field(int id); /*! @overload KDbField* field(int id) */ const KDbField* field(int id) const; /*! Like KDbQuerySchema::field(const QString& name) but returns not only KDbField object for @a identifier but entire KDbQueryColumnInfo object. @a identifier can be: - a fieldname - an aliasname - a tablename.fieldname - a tablename.aliasname Note that if there are two occurrrences of the same name, only the first is accessible using this method. For instance, calling columnInfo("name") for "SELECT t1.name, t2.name FROM t1, t2" statement will only return the column related to t1.name and not t2.name, so you'll need to explicitly specify "t2.name" as the identifier to get the second column. */ KDbQueryColumnInfo* columnInfo(const QString& identifier, bool expanded = true) const; /*! Options used in fieldsExpanded() and visibleFieldsExpanded(). */ enum FieldsExpandedOptions { Default, //!< All fields are returned even if duplicated Unique, //!< Unique list of fields is returned WithInternalFields, //!< Like Default but internal fields (for lookup) are appended WithInternalFieldsAndRecordId //!< Like WithInternalFields but record ID (big int type) field //!< is appended after internal fields }; /*! @return fully expanded list of fields. KDbQuerySchema::fields() returns vector of fields used for the query columns, but in a case when there are asterisks defined for the query, it does not expand KDbQueryAsterisk objects to field lists but return every asterisk as-is. This could be inconvenient when you need just a fully expanded list of fields, so this method does the work for you. If @a options is Unique, each field is returned in the vector only once (first found field is selected). Note however, that the same field can be returned more than once if it has attached a different alias. For example, let t be TABLE( a, b ) and let query be defined by "SELECT *, a AS alfa FROM t" statement. Both fieldsExpanded(Default) and fieldsExpanded(Unique) will return [ a, b, a (alfa) ] list. On the other hand, for query defined by "SELECT *, a FROM t" statement, fieldsExpanded(Default) will return [ a, b, a ] list while fieldsExpanded(Unique) will return [ a, b ] list. If @a options is WithInternalFields or WithInternalFieldsAndRecordID, additional internal fields are also appended to the vector. If @a options is WithInternalFieldsAndRecordId, one fake BigInteger column is appended to make space for Record ID column used by KDbCursor implementations. For example, let city_id in TABLE persons(surname, city_id) reference cities.id in TABLE cities(id, name) and let query q be defined by "SELECT * FROM persons" statement. We want to display persons' city names instead of city_id's. To do this, cities.name has to be retrieved as well, so the following statement should be used: "SELECT * FROM persons, cities.name LEFT OUTER JOIN cities ON persons.city_id=cities.id". Thus, calling fieldsExpanded(WithInternalFieldsAndRecordId) will return 4 elements instead of 2: persons.surname, persons.city_id, cities.name, {ROWID}. The {ROWID} item is the placeholder used for fetching ROWID by KDb cursors. By default, all fields are returned in the vector even if there are multiple occurrences of one or more (options == Default). Note: You should assign the resulted vector in your space - it will be shared and implicity copied on any modification. This method's result is cached by KDbQuerySchema object. @todo js: UPDATE CACHE! */ inline KDbQueryColumnInfo::Vector fieldsExpanded( FieldsExpandedOptions options = Default) const { return fieldsExpandedInternal(options, false); } /*! Like fieldsExpanded() but returns only visible fields. */ inline KDbQueryColumnInfo::Vector visibleFieldsExpanded( FieldsExpandedOptions options = Default) const { return fieldsExpandedInternal(options, true); } /*! @return list of internal fields used for lookup columns. */ KDbQueryColumnInfo::Vector internalFields() const; /*! @return info for expanded of internal field at index @a index. The returned field can be either logical or internal (for lookup), the latter case is true if @a index >= fieldsExpanded().count(). Equivalent of KDbQuerySchema::fieldsExpanded(WithInternalFields).at(index). */ KDbQueryColumnInfo* expandedOrInternalField(int index) const; /*! Options used in columnsOrder(). */ enum ColumnsOrderOptions { UnexpandedList, //!< A map for unexpanded list is created UnexpandedListWithoutAsterisks, //!< A map for unexpanded list is created, with asterisks skipped ExpandedList //!< A map for expanded list is created }; /*! @return a hash for fast lookup of query columns' order. - If @a options is UnexpandedList, each KDbQueryColumnInfo pointer is mapped to the index within (unexpanded) list of fields, i.e. "*" or "table.*" asterisks are considered to be single items. - If @a options is UnexpandedListWithoutAsterisks, each KDbQueryColumnInfo pointer is mapped to the index within (unexpanded) list of columns that come from asterisks like "*" or "table.*" are not included in the map at all. - If @a options is ExpandedList (the default) this method provides is exactly opposite information compared to vector returned by fieldsExpanded(). This method's result is cached by the KDbQuerySchema object. Note: indices of internal fields (see internalFields()) are also returned here - in this case the index is counted as a sum of size(e) + i (where "e" is the list of expanded fields and i is the column index within internal fields list). This feature is used eg. at the end of KDbConnection::updateRecord() where need indices of fields (including internal) to update all the values in memory. Example use: let t be table (int id, name text, surname text) and q be query defined by a statement "select * from t". - columnsOrder(ExpandedList) will return the following map: KDbQueryColumnInfo(id)->0, KDbQueryColumnInfo(name)->1, KDbQueryColumnInfo(surname)->2. - columnsOrder(UnexpandedList) will return the following map: KDbQueryColumnInfo(id)->0, KDbQueryColumnInfo(name)->0, KDbQueryColumnInfo(surname)->0 because the column list is not expanded. This way you can use the returned index to get KDbField* pointer using field(int) method of KDbFieldList superclass. - columnsOrder(UnexpandedListWithoutAsterisks) will return the following map: KDbQueryColumnInfo(id)->0, */ QHash columnsOrder(ColumnsOrderOptions options = ExpandedList) const; /*! @return table describing order of primary key (PKEY) fields within the query. Indexing is performed against vector returned by fieldsExpanded(). It is usable for e.g. Conenction::updateRecord(), when we need to locate each primary key's field in a constant time. Returned vector is owned and cached by KDbQuerySchema object. When you assign it, it is implicity shared. Its size is equal to number of primary key fields defined for master table (masterTable()->primaryKey()->fieldCount()). Each element of the returned vector: - can belong to [0..fieldsExpanded().count()-1] if there is such primary key's field in the fieldsExpanded() list. - can be equal to -1 if there is no such primary key's field in the fieldsExpanded() list. If there are more than one primary key's field included in the query, only first-found column (oin the fieldsExpanded() list) for each pkey's field is included. Returns empty vector if there is no master table or no master table's pkey. @see example for pkeyFieldCount(). @todo js: UPDATE CACHE! */ QVector pkeyFieldsOrder() const; /*! @return number of master table's primary key fields included in this query. This method is useful to quickly check whether the vector returned by pkeyFieldsOrder() if filled completely. User e.g. in KDbConnection::updateRecord() to check if entire primary key information is specified. Examples: let table T has (ID1 INTEGER, ID2 INTEGER, A INTEGER) fields, and let (ID1, ID2) is T's primary key. -# The query defined by "SELECT * FROM T" statement contains all T's primary key's fields as T is the master table, and thus pkeyFieldCount() will return 2 (both primary key's fields are in the fieldsExpanded() list), and pkeyFieldsOrder() will return vector {0, 1}. -# The query defined by "SELECT A, ID2 FROM T" statement, and thus pkeyFieldCount() will return 1 (only one primary key's field is in the fieldsExpanded() list), and pkeyFieldsOrder() will return vector {-1, 1}, as second primary key's field is at position #1 and first field is not specified at all within the query. */ int pkeyFieldCount(); /*! @return a list of field infos for all auto-incremented fields from master table of this query. This result is cached for efficiency. fieldsExpanded() is used for that. */ KDbQueryColumnInfo::List* autoIncrementFields() const; /*! @return a preset statement (if any). */ KDbEscapedString statement() const; /*! Forces a raw SQL statement @a sql for the query. This means that no statement is composed * from KDbQuerySchema's content. */ void setStatement(const KDbEscapedString& sql); /*! @return a string that is a result of concatenating all column names for @a infolist, with "," between each one. This is usable e.g. as argument like "field1,field2" for "INSERT INTO (xxx) ..". The result of this method is effectively cached, and it is invalidated when set of fields changes (e.g. using clear() or addField()). This method is similar to KDbFieldList::sqlFieldsList() it just uses KDbQueryColumnInfo::List instead of KDbField::List. @a escapingType can be used to alter default escaping type. If @a conn is not provided for DriverEscaping, no escaping is performed. */ static KDbEscapedString sqlColumnsList(const KDbQueryColumnInfo::List& infolist, KDbConnection *conn = 0, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping); /*! @return cached list of autoincrement fields created using sqlColumnsList() on a list returned by autoIncrementFields(). The field names are escaped using driver escaping. */ KDbEscapedString autoIncrementSQLFieldsList(KDbConnection *conn) const; - /*! Sets a WHERE expression @a exp. - Previously set WHERE expression will be removed. - You can pass null expression (KDbExpression()) to remove existing WHERE expresssion. */ - void setWhereExpression(const KDbExpression& expr); + /** + * @brief Sets a WHERE expression @a exp. + * + * Previously set WHERE expression will be removed. A null expression + * (KDbExpression()) can be passed to remove existing WHERE expresssion. + * @return @c false if @a expr is not a valid WHERE expression. validate() is called + * to check this. On failure the WHERE expression for this query is cleared. In this + * case a string pointed by @a errorMessage (if provided) is set to a general error + * message and a string pointed by @a errorDescription (if provided) is set to a + * detailed description of the error. + */ + bool setWhereExpression(const KDbExpression &expr, QString *errorMessage = nullptr, + QString *errorDescription = nullptr); /*! @return WHERE expression or 0 if this query has no WHERE expression */ KDbExpression whereExpression() const; - /*! Adds a part to WHERE expression. - Simplifies creating of WHERE expression, if used instead - of setWhereExpression(KDbExpression *expr). */ - void addToWhereExpression(KDbField *field, const QVariant& value, KDbToken relation = '='); + /** + * @brief Appends a part to WHERE expression. + * + * Simplifies creating of WHERE expression if used instead of setWhereExpression(). + * @return @c false if the newly constructed WHERE expression is not valid. + * validate() is called to check this. On failure the WHERE expression for this query + * is left unchanged. In this case a string pointed by @a errorMessage (if provided) + * is set to a general error message and a string pointed by @a errorDescription + * (if provided) is set to a detailed description of the error. + */ + bool addToWhereExpression(KDbField *field, const QVariant &value, + KDbToken relation = '=', QString *errorMessage = nullptr, + QString *errorDescription = nullptr); /*! Sets a list of columns for ORDER BY section of the query. Each name on the list must be a field or alias present within the query and must not be covered by aliases. If one or more names cannot be found within the query, the method will have no effect. Any previous ORDER BY settings will be removed. Note that this information is cleared whenever you call methods that modify list of columns (KDbQueryColumnInfo), i.e. insertField(), addField(), removeField(), addExpression(), etc. (because KDbOrderByColumn items can point to a KDbQueryColumnInfo that's removed by these methods), so you should use setOrderByColumnList() method after the query is completely built. */ void setOrderByColumnList(const KDbOrderByColumnList& list); /*! @return a list of columns listed in ORDER BY section of the query. Read notes for @ref setOrderByColumnList(). */ KDbOrderByColumnList* orderByColumnList(); /*! @see orderByColumnList() */ const KDbOrderByColumnList* orderByColumnList() const; /*! @return query schema parameters. These are taked from the WHERE section (a tree of expression items). */ QList parameters() const; //! @return true if this query is valid /*! Detailed validation is performed in the same way as parsing of query statements * by the KDbParser. * Example :Let the query be "SELECT FROM WHERE ". * First each field from (@see fields()) is validated using - * KDbField::expression().validate(). Then the (@see whereExpression()) + * KDbField::expression().validate(). Then the (@see + * whereExpression()) * is validated using KDbExpression::validate(). * - * On error, a string pointed by @a errorMessage is set to a general error message - * and a string pointed by @a errorDescription is set to a detailed description - * of the error. */ + * On error a string pointed by @a errorMessage (if provided) is set to a general + * error message and a string pointed by @a errorDescription (if provided) is set to a + * detailed description of the error. + */ //! @todo add tests - bool validate(QString *errorMessage, QString *errorDescription); + bool validate(QString *errorMessage = nullptr, QString *errorDescription = nullptr); class Private; protected: //! @internal associates @a conn with this query so it's possible to find tables explicit KDbQuerySchema(KDbConnection *conn); void init(); void computeFieldsExpanded() const; //! Used by fieldsExpanded(FieldsExpandedOptions) //! and visibleFieldsExpanded(FieldsExpandedOptions options). KDbQueryColumnInfo::Vector fieldsExpandedInternal(FieldsExpandedOptions options, bool onlyVisible) const; + /** Internal method used by a query parser. + */ + void setWhereExpressionInternal(const KDbExpression &expr); + Private * const d; }; //! @short KDbQueryAsterisk class encapsulates information about single asterisk in query definition /*! There are two types of query asterisks: 1. "Single-table" asterisk, that references all fields of given table used in the query. Example SQL statement: @code SELECT staff.*, cars.model from staff, cars WHERE staff.car = cars.number; @endcode The "staff.*" element is our "single-table" asterisk; this tells us that we want to get all fields of table "staff". 2. "All-tables" asterisk, that references all fields of all tables used in the query. Example SQL statement: @code SELECT * from staff, cars WHERE staff.car = cars.number; @endcode The "*" is our "all-tables" asterisk; this tells us that we want to get all fields of all used tables (here: "staff" and "cars"). There can be many asterisks of 1st type defined for given single query. There can be one asterisk of 2nd type defined for given single query. */ class KDB_EXPORT KDbQueryAsterisk : public KDbField { public: /*! Constructs query asterisk definition object. Pass table schema to @a table if this asterisk should be of type "single-table", otherwise (if you want to define "all-tables" type asterisk), omit this parameter. KDbQueryAsterisk objects are owned by KDbQuerySchema object (not by KDbTableSchema object like for ordinary KDbField objects) for that the KDbQueryAsterisk object was added (using KDbQuerySchema::addField()). */ explicit KDbQueryAsterisk(KDbQuerySchema *query, KDbTableSchema *table = 0); /*! Constructs a deep copu of query asterisk definition object @a asterisk. */ KDbQueryAsterisk(const KDbQueryAsterisk& asterisk); virtual ~KDbQueryAsterisk(); /*! @return Query object for that this asterisk object is defined */ KDbQuerySchema *query() const; /*! @return Table schema for this asterisk if it has "single-table" type (1st type) or 0 if it has "all-tables" type (2nd type) defined. */ virtual KDbTableSchema* table() const; /*! Sets table schema for this asterisk. @a table may be NULL - then the asterisk becames "all-tables" type asterisk. */ virtual void setTable(KDbTableSchema *table); /*! This is convenience method that returns true if the asterisk has "all-tables" type (2nd type).*/ bool isSingleTableAsterisk() const; /*! This is convenience method that returns true if the asterisk has "single-tables" type (2nd type).*/ bool isAllTableAsterisk() const; protected: //! @return a deep copy of this object. Used in KDbFieldList(const KDbFieldList& fl). virtual KDbField* copy() const; /*! Table schema for this asterisk */ KDbTableSchema* m_table; friend class KDbQuerySchema; }; //! Sends order-by-column information @a order to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbOrderByColumn& order); //! Sends order-by-column-list information @a list to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbOrderByColumnList& list); //! Sends query schema information @a query to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbQuerySchema& query); //! Sends query asterisk information @a asterisk to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbQueryAsterisk& asterisk); #endif diff --git a/src/KDbQuerySchema_p.h b/src/KDbQuerySchema_p.h index 8b68a14a..8af4fef9 100644 --- a/src/KDbQuerySchema_p.h +++ b/src/KDbQuerySchema_p.h @@ -1,215 +1,221 @@ /* This file is part of the KDE project Copyright (C) 2003-2016 Jarosław Staniek 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. */ #ifndef KDB_QUERYSCHEMA_P_H #define KDB_QUERYSCHEMA_P_H #include "KDbDriver.h" #include "KDbExpression.h" #include "KDbQuerySchema.h" #include #include class KDbConnection; //! @internal class KDbQuerySchema::Private { Q_DECLARE_TR_FUNCTIONS(KDbQuerySchema) public: explicit Private(KDbQuerySchema* q, Private* copy = 0); ~Private(); //! @return a new query that's associated with @a conn. Used internally, e.g. by the parser. //! Uses an internal KDbQuerySchema(KDbConnection*) ctor. static KDbQuerySchema* createQuery(KDbConnection *conn); void clear(); void clearCachedData(); void setColumnAlias(int position, const QString& alias); inline void setTableAlias(int position, const QString& alias) { tableAliases.insert(position, alias.toLower()); tablePositionsForAliases.insert(alias.toLower(), position); } inline int columnAliasesCount() { tryRegenerateExprAliases(); return columnAliases.count(); } inline QString columnAlias(int position) { tryRegenerateExprAliases(); return columnAliases.value(position); } inline bool hasColumnAlias(int position) { tryRegenerateExprAliases(); return columnAliases.contains(position); } inline void removeTablePositionForAlias(const QString& alias) { tablePositionsForAliases.remove(alias.toLower()); } inline int tablePositionForAlias(const QString& alias) const { return tablePositionsForAliases.value(alias.toLower(), -1); } inline int columnPositionForAlias(const QString& alias) const { return columnPositionsForAliases.value(alias.toLower(), -1); } + //! Accessor for buildSelectQuery() + static void setWhereExpressionInternal(KDbQuerySchema *query, const KDbExpression &expr) + { + query->d->whereExpr = expr; + } + KDbQuerySchema *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. */ KDbTableSchema *masterTable; /*! List of tables used in this query */ QList tables; KDbField *fakeRecordIdField; //! used to mark a place for record Id KDbQueryColumnInfo *fakeRecordIdCol; //! used to mark a place for record Id //! Connection on which this query operates //! @todo use equivalent of QPointer KDbConnection* conn; protected: void tryRegenerateExprAliases(); void setColumnAliasInternal(int position, const QString& alias); /*! Used to mapping columns to its aliases for this query */ QHash columnAliases; /*! Collects table positions for aliases: used in tablePositionForAlias(). */ QHash tablePositionsForAliases; /*! Collects column positions for aliases: used in columnPositionForAlias(). */ QHash columnPositionsForAliases; public: /*! Used to mapping tables to its aliases for this query */ QHash tableAliases; /*! Helper used with aliases */ int maxIndexWithAlias; /*! Used to store visibility flag for every field */ QBitArray visibility; /*! List of asterisks defined for this query */ KDbField::List asterisks; /*! Temporary field vector for using in fieldsExpanded() */ KDbQueryColumnInfo::Vector *fieldsExpanded; /*! Like fieldsExpanded but only visible column infos; infos are not owned. */ KDbQueryColumnInfo::Vector *visibleFieldsExpanded; /*! Temporary field vector containing internal fields used for lookup columns. */ KDbQueryColumnInfo::Vector *internalFields; /*! Temporary, used to cache sum of expanded fields and internal fields (+record Id) used for lookup columns. Contains not auto-deleted items.*/ KDbQueryColumnInfo::Vector *fieldsExpandedWithInternalAndRecordId; /*! Like fieldsExpandedWithInternalAndRecordId but only contains visible column infos; infos are not owned.*/ KDbQueryColumnInfo::Vector *visibleFieldsExpandedWithInternalAndRecordId; /*! Temporary, used to cache sum of expanded fields and internal fields used for lookup columns. Contains not auto-deleted items.*/ KDbQueryColumnInfo::Vector *fieldsExpandedWithInternal; /*! Like fieldsExpandedWithInternal but only contains visible column infos; infos are not owned.*/ KDbQueryColumnInfo::Vector *visibleFieldsExpandedWithInternal; /*! A list of fields for ORDER BY section. @see KDbQuerySchema::orderByColumnList(). */ KDbOrderByColumnList* orderByColumnList; /*! A cache for autoIncrementFields(). */ KDbQueryColumnInfo::List *autoincFields; /*! A cache for autoIncrementSQLFieldsList(). */ KDbEscapedString autoIncrementSQLFieldsList; QWeakPointer lastUsedDriverForAutoIncrementSQLFieldsList; /*! A hash for fast lookup of query columns' order (unexpanded version). */ QHash *columnsOrder; /*! A hash for fast lookup of query columns' order (unexpanded version without asterisks). */ QHash *columnsOrderWithoutAsterisks; /*! A hash for fast lookup of query columns' order. This is exactly opposite information compared to vector returned by fieldsExpanded() */ QHash *columnsOrderExpanded; /*! order of PKEY fields (e.g. for updateRecord() ) */ QVector *pkeyFieldsOrder; /*! number of PKEY fields within the query */ int pkeyFieldCount; /*! Forced (predefined) raw SQL statement */ KDbEscapedString sql; /*! Relationships defined for this query. */ QList relations; /*! Information about columns bound to tables. Used if table is used in FROM section more than once (using table aliases). This list is updated by insertField(int position, KDbField *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" */ QVector tablesBoundToColumns; /*! WHERE expression */ KDbExpression whereExpr; QHash columnInfosByNameExpanded; QHash columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped //! field schemas created for multiple joined columns like a||' '||b||' '||c KDbField::List *ownedVisibleColumns; /*! Set by insertField(): true, if aliases for expression columns should be generated on next columnAlias() call. */ bool regenerateExprAliases; }; #endif diff --git a/src/parser/KDbParser_p.cpp b/src/parser/KDbParser_p.cpp index 5a3130b4..35ce23db 100644 --- a/src/parser/KDbParser_p.cpp +++ b/src/parser/KDbParser_p.cpp @@ -1,510 +1,511 @@ /* This file is part of the KDE project - Copyright (C) 2004-2016 Jarosław Staniek + Copyright (C) 2004-2017 Jarosław Staniek 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 "KDbParser_p.h" #include "KDb.h" #include "KDbConnection.h" #include "KDbTableSchema.h" #include "KDbQuerySchema.h" +#include "KDbQuerySchema_p.h" #include "kdb_debug.h" #include "generated/sqlparser.h" #include #include KDbParser *globalParser = 0; KDbField *globalField = 0; QList fieldList; int globalCurrentPos = 0; QByteArray globalToken; extern int yylex_destroy(void); //------------------------------------- KDbParserPrivate::KDbParserPrivate() : table(0), query(0), connection(0), initialized(false) { reset(); } KDbParserPrivate::~KDbParserPrivate() { reset(); } void KDbParserPrivate::reset() { statementType = KDbParser::NoType; sql.clear(); error = KDbParserError(); delete table; table = 0; delete query; query = 0; } void KDbParserPrivate::setStatementType(KDbParser::StatementType type) { statementType = type; } void KDbParserPrivate::setError(const KDbParserError &err) { error = err; } void KDbParserPrivate::setTableSchema(KDbTableSchema *table) { delete this->table; this->table = table; } void KDbParserPrivate::setQuerySchema(KDbQuerySchema *query) { delete this->query; this->query = query; } //------------------------------------- KDbParseInfo::KDbParseInfo(KDbQuerySchema *query) : d(new Private) { d->querySchema = query; } KDbParseInfo::~KDbParseInfo() { delete d; } QList KDbParseInfo::tablesAndAliasesForName(const QString &tableOrAliasName) const { const QList *list = d->repeatedTablesAndAliases.value(tableOrAliasName); return list ? *list : QList(); } KDbQuerySchema* KDbParseInfo::querySchema() const { return d->querySchema; } QString KDbParseInfo::errorMessage() const { return d->errorMessage; } QString KDbParseInfo::errorDescription() const { return d->errorDescription; } void KDbParseInfo::setErrorMessage(const QString &message) { d->errorMessage = message; } void KDbParseInfo::setErrorDescription(const QString &description) { d->errorDescription = description; } //------------------------------------- KDbParseInfoInternal::KDbParseInfoInternal(KDbQuerySchema *query) : KDbParseInfo(query) { } KDbParseInfoInternal::~KDbParseInfoInternal() { } void KDbParseInfoInternal::appendPositionForTableOrAliasName(const QString &tableOrAliasName, int pos) { QList *list = d->repeatedTablesAndAliases.value(tableOrAliasName); if (!list) { list = new QList(); d->repeatedTablesAndAliases.insert(tableOrAliasName, list); } list->append(pos); } //------------------------------------- extern int yyparse(); extern void tokenize(const char *data); void yyerror(const char *str) { kdbDebug() << "error: " << str; kdbDebug() << "at character " << globalCurrentPos << " near tooken " << globalToken; KDbParserPrivate::get(globalParser)->setStatementType(KDbParser::NoType); const bool otherError = (qstrnicmp(str, "other error", 11) == 0); const bool syntaxError = qstrnicmp(str, "syntax error", 12) == 0; if (( globalParser->error().type().isEmpty() && (str == 0 || strlen(str) == 0 || syntaxError)) || otherError) { kdbDebug() << globalParser->statement(); QString ptrline(globalCurrentPos, QLatin1Char(' ')); ptrline += QLatin1String("^"); kdbDebug() << ptrline; #if 0 //lexer may add error messages QString lexerErr = globalParser->error().message(); QString errtypestr = QLatin1String(str); if (lexerErr.isEmpty()) { if (errtypestr.startsWith(QString::fromLatin1("parse error, expecting `IDENTIFIER'"))) { lexerErr = KDbParser::tr("identifier was expected"); } } #endif //! @todo exact invalid expression can be selected in the editor, based on KDbParseInfo data if (!otherError) { const bool isKDbSQLKeyword = KDb::isKDbSQLKeyword(globalToken); if (isKDbSQLKeyword || syntaxError) { if (isKDbSQLKeyword) { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"), KDbParser::tr("\"%1\" is a reserved keyword.").arg(QLatin1String(globalToken)), globalToken, globalCurrentPos)); } else { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"), KDbParser::tr("Syntax error."), globalToken, globalCurrentPos)); } } else { KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Error"), KDbParser::tr("Error near \"%1\".").arg(QLatin1String(globalToken)), globalToken, globalCurrentPos)); } } } } void setError(const QString& errName, const QString& errDesc) { KDbParserPrivate::get(globalParser)->setError(KDbParserError(errName, errDesc, globalToken, globalCurrentPos)); yyerror(qPrintable(errName)); } void setError(const QString& errDesc) { setError(KDbParser::tr("Other error"), errDesc); } /* this is better than assert() */ #define IMPL_ERROR(errmsg) setError(KDbParser::tr("Implementation error"), QLatin1String(errmsg)) //! @internal Parses @a data for parser @a p //! @todo Make it REENTRANT bool parseData(KDbParser *p, const KDbEscapedString &sql) { globalParser = p; globalParser->reset(); globalField = 0; fieldList.clear(); if (sql.isEmpty()) { KDbParserError err(KDbParser::tr("Error"), KDbParser::tr("No query statement specified."), globalToken, globalCurrentPos); KDbParserPrivate::get(globalParser)->setError(err); yyerror(""); globalParser = 0; return false; } const char *data = sql.constData(); tokenize(data); if (!globalParser->error().type().isEmpty()) { globalParser = 0; return false; } bool ok = yyparse() == 0; if (ok && globalCurrentPos < sql.length()) { kdbDebug() << "Parse error: tokens left" << "globalCurrentPos:" << globalCurrentPos << "sql.length():" << sql.length() << "globalToken:" << QString::fromUtf8(globalToken); KDbParserError err(KDbParser::tr("Error"), KDbParser::tr("Unexpected character."), globalToken, globalCurrentPos); KDbParserPrivate::get(globalParser)->setError(err); yyerror(""); ok = false; } if (ok && globalParser->statementType() == KDbParser::Select) { kdbDebug() << "parseData(): ok"; // kdbDebug() << "parseData(): " << tableDict.count() << " loaded tables"; /* KDbTableSchema *ts; for(QDictIterator it(tableDict); KDbTableSchema *s = tableList.first(); s; s = tableList.next()) { kdbDebug() << " " << s->name(); }*/ } else { ok = false; } yylex_destroy(); globalParser = 0; return ok; } /*! Adds @a columnExpr to @a parseInfo The column can be in a form table.field, tableAlias.field or field. @return true on success. On error message in globalParser object is updated. */ bool addColumn(KDbParseInfo *parseInfo, const KDbExpression &columnExpr) { if (!KDbExpression(columnExpr).validate(parseInfo)) { // (KDbExpression(columnExpr) used to avoid constness problem) setError(parseInfo->errorMessage(), parseInfo->errorDescription()); return false; } const KDbVariableExpression v_e(columnExpr.toVariable()); if (columnExpr.expressionClass() == KDb::VariableExpression && !v_e.isNull()) { //it's a variable: if (v_e.name() == QLatin1String("*")) {//all tables asterisk if (parseInfo->querySchema()->tables()->isEmpty()) { setError(KDbParser::tr("\"*\" could not be used if no tables are specified.")); return false; } KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema()); if (!parseInfo->querySchema()->addAsterisk(a)) { delete a; setError(KDbParser::tr("\"*\" could not be added.")); return false; } } else if (v_e.tableForQueryAsterisk()) {//one-table asterisk KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema(), v_e.tableForQueryAsterisk()); if (!parseInfo->querySchema()->addAsterisk(a)) { delete a; setError(KDbParser::tr("\".*\" could not be added.")); return false; } } else if (v_e.field()) {//"table.field" or "field" (bound to a table or not) if (!parseInfo->querySchema()->addField(v_e.field(), v_e.tablePositionForField())) { setError(KDbParser::tr("Could not add binding to a field.")); return false; } } else { IMPL_ERROR("addColumn(): unknown case!"); return false; } return true; } //it's complex expression return parseInfo->querySchema()->addExpression(columnExpr); } KDbQuerySchema* buildSelectQuery( KDbQuerySchema* querySchema, KDbNArgExpression* _colViews, KDbNArgExpression* _tablesList, SelectOptionsInternal* options) { KDbParseInfoInternal parseInfo(querySchema); // remove from heap (using heap was requered because parser uses union) KDbNArgExpression colViews; if (_colViews) { colViews = *_colViews; delete _colViews; } KDbNArgExpression tablesList; if (_tablesList) { tablesList = *_tablesList; delete _tablesList; } QScopedPointer optionsPtr(options); QScopedPointer querySchemaPtr(querySchema); // destroy query on any error //-------tables list int columnNum = 0; /*! @todo use this later if there are columns that use database fields, e.g. "SELECT 1 from table1 t, table2 t") is ok however. */ //used to collect information about first repeated table name or alias: if (!tablesList.isEmpty()) { for (int i = 0; i < tablesList.argCount(); i++, columnNum++) { KDbExpression e(tablesList.arg(i)); KDbVariableExpression t_e; QString aliasString; if (e.expressionClass() == KDb::SpecialBinaryExpression) { KDbBinaryExpression t_with_alias = e.toBinary(); Q_ASSERT(e.isBinary()); Q_ASSERT(t_with_alias.left().expressionClass() == KDb::VariableExpression); Q_ASSERT(t_with_alias.right().expressionClass() == KDb::VariableExpression && (t_with_alias.token() == KDbToken::AS || t_with_alias.token() == KDbToken::AS_EMPTY)); t_e = t_with_alias.left().toVariable(); aliasString = t_with_alias.right().toVariable().name(); } else { t_e = e.toVariable(); } Q_ASSERT(t_e.isVariable()); QString tname = t_e.name(); KDbTableSchema *s = globalParser->connection()->tableSchema(tname); if (!s) { setError(KDbParser::tr("Table \"%1\" does not exist.").arg(tname)); return 0; } QString tableOrAliasName = KDb::iifNotEmpty(aliasString, tname); if (!aliasString.isEmpty()) { // kdbDebug() << "- add alias for table: " << aliasString; } // 1. collect information about first repeated table name or alias // (potential ambiguity) parseInfo.appendPositionForTableOrAliasName(tableOrAliasName, i); // kdbDebug() << "addTable: " << tname; querySchema->addTable(s, aliasString); } } /* set parent table if there's only one */ if (querySchema->tables()->count() == 1) querySchema->setMasterTable(querySchema->tables()->first()); //-------add fields if (!colViews.isEmpty()) { columnNum = 0; bool containsAsteriskColumn = false; // used to check duplicated asterisks (disallowed) for (int i = 0; i < colViews.argCount(); i++, columnNum++) { const KDbExpression e(colViews.arg(i)); KDbExpression columnExpr(e); KDbVariableExpression aliasVariable; if (e.expressionClass() == KDb::SpecialBinaryExpression && e.isBinary() && (e.token() == KDbToken::AS || e.token() == KDbToken::AS_EMPTY)) { //KDb::SpecialBinaryExpression: with alias columnExpr = e.toBinary().left(); aliasVariable = e.toBinary().right().toVariable(); if (aliasVariable.isNull()) { setError(KDbParser::tr("Invalid alias definition for column \"%1\".") .arg(columnExpr.toString(0).toString())); //ok? break; } } const KDb::ExpressionClass c = columnExpr.expressionClass(); const bool isExpressionField = c == KDb::ConstExpression || c == KDb::UnaryExpression || c == KDb::ArithmeticExpression || c == KDb::LogicalExpression || c == KDb::RelationalExpression || c == KDb::FunctionExpression || c == KDb::AggregationExpression; if (c == KDb::VariableExpression) { if (columnExpr.toVariable().name() == QLatin1String("*")) { if (containsAsteriskColumn) { setError(KDbParser::tr("More than one asterisk \"*\" is not allowed.")); return 0; } else { containsAsteriskColumn = true; } } // addColumn() will handle this } else if (isExpressionField) { //expression object will be reused, take, will be owned, do not destroy // kdbDebug() << colViews->list.count() << " " << it.current()->debugString(); //! @todo IMPORTANT: it.remove(); } else if (aliasVariable.isNull()) { setError(KDbParser::tr("Invalid \"%1\" column definition.") .arg(e.toString(0).toString())); //ok? break; } else { //take first (left) argument of the special binary expr, will be owned, do not destroy e.toBinary().setLeft(KDbExpression()); } if (!addColumn(&parseInfo, columnExpr)) { break; } if (!aliasVariable.isNull()) { // kdbDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " // << columnNum; querySchema->setColumnAlias(columnNum, aliasVariable.name()); } } // for if (!globalParser->error().message().isEmpty()) { // we could not return earlier (inside the loop) // because we want run CLEANUP what could crash QMutableListIterator. return 0; } } //----- SELECT options if (options) { //----- WHERE expr. if (!options->whereExpr.isNull()) { if (!options->whereExpr.validate(&parseInfo)) { setError(parseInfo.errorMessage(), parseInfo.errorDescription()); return 0; } - querySchema->setWhereExpression(options->whereExpr); + KDbQuerySchema::Private::setWhereExpressionInternal(querySchema, options->whereExpr); } //----- ORDER BY if (options->orderByColumns) { KDbOrderByColumnList *orderByColumnList = querySchema->orderByColumnList(); int count = options->orderByColumns->count(); QList::ConstIterator it(options->orderByColumns->constEnd()); --it; for (;count > 0; --it, --count) /*opposite direction due to parser specifics*/ { //first, try to find a column name or alias (outside of asterisks) KDbQueryColumnInfo *columnInfo = querySchema->columnInfo((*it).aliasOrName, false/*outside of asterisks*/); if (columnInfo) { orderByColumnList->appendColumn(columnInfo, (*it).ascending); } else { //failed, try to find a field name within all the tables if ((*it).columnNumber != -1) { if (!orderByColumnList->appendColumn(querySchema, (*it).ascending, (*it).columnNumber - 1)) { setError(KDbParser::tr("Could not define sorting. Column at " "position %1 does not exist.") .arg((*it).columnNumber)); return 0; } } else { KDbField * f = querySchema->findTableField((*it).aliasOrName); if (!f) { setError(KDbParser::tr("Could not define sorting. " "Column name or alias \"%1\" does not exist.") .arg((*it).aliasOrName)); return 0; } orderByColumnList->appendField(f, (*it).ascending); } } } } } // kdbDebug() << "Select ColViews=" << (colViews ? colViews->debugString() : QString()) // << " Tables=" << (tablesList ? tablesList->debugString() : QString()s); return querySchemaPtr.take(); }