diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -43,6 +43,7 @@ DriverTest.cpp ExpressionsTest.cpp MissingTableTest.cpp + OrderByColumnTest.cpp QuerySchemaTest.cpp KDbTest.cpp @@ -52,6 +53,8 @@ target_compile_definitions(MissingTableTest PRIVATE -DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) +target_compile_definitions(OrderByColumnTest PRIVATE KDB_DEPRECATED=) + if(NOT WIN32) #TODO enable for Windows when headers_test.sh is ported e.g. to python add_subdirectory(headers) endif() diff --git a/autotests/OrderByColumnTest.h b/autotests/OrderByColumnTest.h new file mode 100644 --- /dev/null +++ b/autotests/OrderByColumnTest.h @@ -0,0 +1,46 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Jarosław Staniek + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KDBORDERBYCOLUMNTEST_H +#define KDBORDERBYCOLUMNTEST_H + +#include "KDbTestUtils.h" + +class OrderByColumnTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + + //! Test ORDER BY data for "SELECT 'foo'" query + void testSelect1Query(); + + //! Test ORDER BY data for "SELECT * FROM cars ORDER BY 2" + void testOrderByIndex(); + + //! Test ORDER BY data for "SELECT * FROM cars ORDER BY model" + void testOrderByColumnName(); + + void cleanupTestCase(); + +private: + KDbTestUtils utils; +}; + +#endif diff --git a/autotests/OrderByColumnTest.cpp b/autotests/OrderByColumnTest.cpp new file mode 100644 --- /dev/null +++ b/autotests/OrderByColumnTest.cpp @@ -0,0 +1,212 @@ +/* This file is part of the KDE project + Copyright (C) 2018 Jarosław Staniek + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "OrderByColumnTest.h" + +#include +#include +#include +#include +#include + +#include + +QTEST_GUILESS_MAIN(OrderByColumnTest) + +void OrderByColumnTest::initTestCase() +{ +} + +void OrderByColumnTest::testSelect1Query() +{ + QVERIFY(utils.testCreateDbWithTables("OrderByColumnTest")); + KDbQuerySchema query; + KDbField *oneField = new KDbField; + oneField->setExpression(KDbConstExpression(KDbToken::CHARACTER_STRING_LITERAL, "foo")); + query.addField(oneField); + KDbOrderByColumnList* orderBy = query.orderByColumnList(); + QVERIFY(orderBy); + QVERIFY(orderBy->isEmpty()); + QCOMPARE(orderBy->count(), 0); + orderBy->appendField(oneField); + KDbConnection *conn = utils.connection.data(); + + // automatic alias "expr1" + KDbEscapedString sql; + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, "SELECT 'foo' AS expr1 ORDER BY expr1"); + QVERIFY(!orderBy->isEmpty()); + QCOMPARE(orderBy->count(), 1); + const int indexOfField = query.indexOf(*oneField); + QCOMPARE(indexOfField, 0); + const QString alias(query.columnAlias(indexOfField)); + QVERIFY(!alias.isEmpty()); + KDbOrderByColumn *orderByColumn = orderBy->value(indexOfField); + QVERIFY(orderByColumn); + QVERIFY(!orderByColumn->column()); + QCOMPARE(orderByColumn->field(), oneField); + QVERIFY(!orderBy->value(orderBy->count() + 10)); + KDbEscapedString orderBySqlOldApi = orderBy->toSqlString(true, conn, KDb::KDbEscaping); + QCOMPARE(orderBySqlOldApi, ""); // alias is not used + KDbEscapedString orderBySql = orderBy->toSqlString(true, conn, &query, KDb::KDbEscaping); + QCOMPARE(orderBySql, alias); // alias is used to point to the column "'foo'" + + // change alias to something other than valid ID + QVERIFY(query.setColumnAlias(indexOfField, "0")); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, "SELECT 'foo' AS \"0\" ORDER BY \"0\""); + orderBySqlOldApi = orderBy->toSqlString(true, conn, KDb::KDbEscaping); + QCOMPARE(orderBySqlOldApi, ""); // alias is not used + orderBySql = orderBy->toSqlString(true, conn, &query, KDb::KDbEscaping); + QCOMPARE(orderBySql, "\"0\""); // alias is used to point to the column "'foo'" +} + +void OrderByColumnTest::testOrderByIndex() +{ + QVERIFY(utils.testCreateDbWithTables("OrderByColumnTest")); + KDbQuerySchema query; + KDbTableSchema *carsTable = utils.connection->tableSchema("cars"); + QVERIFY(carsTable); + query.addTable(carsTable); + query.addAsterisk(new KDbQueryAsterisk(&query)); + KDbOrderByColumnList* orderBy = query.orderByColumnList(); + KDbConnection *conn = utils.connection.data(); + + // "SELECT * FROM cars ORDER BY model ASC, owner DESC" + QVERIFY(query.orderByColumnList()->isEmpty()); + QVERIFY(orderBy->appendColumn(conn, &query, + KDbOrderByColumn::SortOrder::Ascending, 2)); + QVERIFY(orderBy->appendColumn(conn, &query, + KDbOrderByColumn::SortOrder::Descending, 1)); + KDbEscapedString sql; + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, "SELECT cars.* FROM cars ORDER BY 3, 2 DESC"); + + QVERIFY2(!orderBy->appendColumn(conn, &query, + KDbOrderByColumn::SortOrder::Ascending, 3), + "appendField for null"); +} + +void OrderByColumnTest::testOrderByColumnName() +{ + QVERIFY(utils.testCreateDbWithTables("OrderByColumnTest")); + KDbQuerySchema query; + KDbTableSchema *carsTable = utils.connection->tableSchema("cars"); + QVERIFY(carsTable); + query.addTable(carsTable); + query.addAsterisk(new KDbQueryAsterisk(&query)); + + // "SELECT * FROM cars ORDER BY model, owner" + QVERIFY(query.orderByColumnList()); + QVERIFY(query.orderByColumnList()->isEmpty()); + QCOMPARE(query.orderByColumnList()->count(), 0); + + KDbOrderByColumnList* orderBy = query.orderByColumnList(); + QVERIFY(orderBy); + QVERIFY(orderBy->isEmpty()); + KDbField *modelField = carsTable->field("model"); + QVERIFY(modelField); + KDbField *ownerField = carsTable->field("owner"); + QVERIFY(ownerField); + orderBy->appendField(modelField); + orderBy->appendField(ownerField); + KDbConnection *conn = utils.connection.data(); + KDbEscapedString orderBySql = orderBy->toSqlString(true, conn, &query, KDb::KDbEscaping); + QCOMPARE(orderBySql, "cars.model, cars.owner"); + + KDbEscapedString sql; + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, "SELECT cars.* FROM cars ORDER BY model, owner"); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, "SELECT [cars].* FROM [cars] ORDER BY [model] COLLATE '', [owner]"); + + // "SELECT * FROM cars ORDER BY model ASC, owner DESC" + orderBy->clear(); + QVERIFY(query.orderByColumnList()->isEmpty()); + orderBy->appendField(modelField, KDbOrderByColumn::SortOrder::Ascending); + orderBy->appendField(ownerField, KDbOrderByColumn::SortOrder::Descending); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + const char validSelect1[] = "SELECT cars.* FROM cars ORDER BY model, owner DESC"; + QCOMPARE(sql, validSelect1); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + const char validDriverSelect1[] = "SELECT [cars].* FROM [cars] ORDER BY [model] COLLATE '', [owner] DESC"; + QCOMPARE(sql, validDriverSelect1); + + // The same query, adding null field + orderBy->clear(); + QVERIFY(query.orderByColumnList()->isEmpty()); + orderBy->appendField(nullptr); + QVERIFY2(query.orderByColumnList()->isEmpty(), "Adding null fields should not affect OREDR BY"); + orderBy->appendField(modelField, KDbOrderByColumn::SortOrder::Ascending); + orderBy->appendField(ownerField, KDbOrderByColumn::SortOrder::Descending); + orderBy->appendField(nullptr); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validSelect1); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validDriverSelect1); + + // The same query, overload + orderBy->clear(); + QVERIFY(query.orderByColumnList()->isEmpty()); + QVERIFY(orderBy->appendFields(conn, &query, + "model", KDbOrderByColumn::SortOrder::Ascending, + "owner", KDbOrderByColumn::SortOrder::Descending)); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validSelect1); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validDriverSelect1); + + // The same query, overload + orderBy->clear(); + QVERIFY(query.orderByColumnList()->isEmpty()); + QVERIFY(orderBy->appendField(conn, &query, "model", KDbOrderByColumn::SortOrder::Ascending)); + QVERIFY(orderBy->appendField(conn, &query, "owner", KDbOrderByColumn::SortOrder::Descending)); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validSelect1); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validDriverSelect1); + + QCOMPARE(orderBy->count(), 2); + QVERIFY2(!orderBy->appendField(conn, &query, ""), "appendField for null"); + QCOMPARE(orderBy->count(), 2); + + // The same query, overload + orderBy->clear(); + QCOMPARE(orderBy->count(), 0); + QVERIFY(query.orderByColumnList()->isEmpty()); + KDbQueryColumnInfo::Vector columns = query.fieldsExpanded(conn); + KDbQueryColumnInfo *ownerColumnInfo = columns.value(1); + QVERIFY(ownerColumnInfo); + KDbQueryColumnInfo *modelColumnInfo = columns.value(2); + QVERIFY(modelColumnInfo); + orderBy->appendColumn(modelColumnInfo, KDbOrderByColumn::SortOrder::Ascending); + orderBy->appendColumn(ownerColumnInfo, KDbOrderByColumn::SortOrder::Descending); + QCOMPARE(orderBy->count(), 2); + QVERIFY(utils.kdbBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validSelect1); + QVERIFY(utils.driverBuilder()->generateSelectStatement(&sql, &query)); + QCOMPARE(sql, validDriverSelect1); +} + +//! @todo Test KDbQuerySchema::setOrderByColumnList +//! @todo Test more KDbOrderByColumnList and KDbOrderByColumn + +void OrderByColumnTest::cleanupTestCase() +{ +} diff --git a/src/KDbNativeStatementBuilder.cpp b/src/KDbNativeStatementBuilder.cpp --- a/src/KDbNativeStatementBuilder.cpp +++ b/src/KDbNativeStatementBuilder.cpp @@ -389,7 +389,7 @@ // ORDER BY KDbEscapedString orderByString(querySchema->orderByColumnList()->toSqlString( - !singleTable /*includeTableName*/, connection, dialect)); + !singleTable /*includeTableName*/, connection, querySchema, dialect)); const QVector pkeyFieldsOrder(querySchema->pkeyFieldsOrder(connection)); if (dialect == KDb::DriverEscaping && orderByString.isEmpty() && !pkeyFieldsOrder.isEmpty()) { // Native only: add automatic ORDER BY if there is no explicitly defined one @@ -408,7 +408,7 @@ automaticPKOrderBy.appendColumn(ci); } orderByString = automaticPKOrderBy.toSqlString(!singleTable /*includeTableName*/, - connection, dialect); + connection, querySchema, dialect); } if (!orderByString.isEmpty()) sql += (" ORDER BY " + orderByString); diff --git a/src/KDbOrderByColumn.h b/src/KDbOrderByColumn.h --- a/src/KDbOrderByColumn.h +++ b/src/KDbOrderByColumn.h @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2018 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 @@ -89,13 +89,26 @@ //! @since 3.1 inline bool operator!=(const KDbOrderByColumn &other) const { return !operator==(other); } - /*! @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). + /** Return an SQL string like "name ASC" or "2 DESC" usable for building an SQL statement + * + * If @a includeTableNames is @c true fields that are related to a table are + * printed as "tablename.fieldname". + * + * @a escapingType can be used to alter default escaping type. + * If @a conn is not provided for DriverEscaping, no escaping is performed. + * If @a query is provided, it can be used to obtain alias information. + * + * @since 3.2 + */ + KDbEscapedString toSqlString(bool includeTableName, + KDbConnection *conn, KDbQuerySchema *query, + KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; - @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, + /*! @overload + + @deprecated since 3.2, use overload that also takes query schema + */ + KDB_DEPRECATED KDbEscapedString toSqlString(bool includeTableName = true, KDbConnection *conn = nullptr, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; @@ -201,15 +214,29 @@ */ QList::ConstIterator constEnd() const; - /*! @return an SQL 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". + /** Return an SQL string like "name ASC, 2 DESC" usable for building an SQL statement + * + * If @a includeTableNames is @c true (the default) fields that are related to a table are + * printed as "tablename.fieldname". + * + * @a escapingType can be used to alter default escaping type. + * If @a conn is not provided for DriverEscaping, no escaping is performed. + * If @a query is provided, it can be used to obtain alias information. + * + * @since 3.2 + */ + KDbEscapedString toSqlString(bool includeTableNames, + KDbConnection *conn, KDbQuerySchema *query, + KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; + + /*! @overload - @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, + @deprecated since 3.2, use overload that also takes query schema + */ + KDB_DEPRECATED KDbEscapedString toSqlString(bool includeTableNames = true, KDbConnection *conn = nullptr, KDb::IdentifierEscapingType escapingType = KDb::DriverEscaping) const; + private: class Private; Private * const d; diff --git a/src/KDbOrderByColumn.cpp b/src/KDbOrderByColumn.cpp --- a/src/KDbOrderByColumn.cpp +++ b/src/KDbOrderByColumn.cpp @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2018 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 @@ -27,28 +27,53 @@ { public: Private() - : column(nullptr) + : columnIndex(-1) , pos(-1) , field(nullptr) , order(KDbOrderByColumn::SortOrder::Ascending) { } Private(const Private &other) { copy(other); } -#define KDbOrderByColumnPrivateArgs(o) std::tie(o.column, o.pos, o.field, o.order) - Private(KDbQueryColumnInfo* aColumn, int aPos, KDbField* aField, KDbOrderByColumn::SortOrder aOrder) { - KDbOrderByColumnPrivateArgs((*this)) = std::tie(aColumn, aPos, aField, aOrder); +#define KDbOrderByColumnPrivateArgs(o) std::tie(o.querySchema, o.connection, o.columnIndex, o.pos, o.field, o.order) + Private(KDbQueryColumnInfo* aColumn, int aPos, KDbField* aField, KDbOrderByColumn::SortOrder aOrder) + { + const KDbQuerySchema *foundQuerySchema = nullptr; + KDbConnection *foundConnection = nullptr; + int foundColumnIndex = -1; + if (aColumn) { + foundQuerySchema =aColumn->querySchema(); + foundConnection = aColumn->connection(); + const KDbQueryColumnInfo::Vector fieldsExpanded = foundQuerySchema->fieldsExpanded( + foundConnection, KDbQuerySchema::FieldsExpandedMode::WithInternalFields); + foundColumnIndex = fieldsExpanded.indexOf(aColumn); + if (foundColumnIndex < 0) { + kdbWarning() << "Column not found in query:" << *aColumn; + } + } + KDbOrderByColumnPrivateArgs((*this)) + = std::tie(foundQuerySchema, foundConnection, foundColumnIndex, aPos, aField, aOrder); } void copy(const Private &other) { KDbOrderByColumnPrivateArgs((*this)) = KDbOrderByColumnPrivateArgs(other); } bool operator==(const Private &other) const { return KDbOrderByColumnPrivateArgs((*this)) == KDbOrderByColumnPrivateArgs(other); } - //! Column to sort, @c nullptr if field is non-0. - KDbQueryColumnInfo* column; + //! Query schema that owns the KDbQueryColumnInfo and thus also this KDbOrderByColumn object. + //! Cached for performance, can be cached since lifetime of the KDbOrderByColumn object depends + //! on the query. @c nullptr if columnIndex is not provided. @since 3.2 + const KDbQuerySchema *querySchema = nullptr; + + //! Connection used to compute expanded fields. Like querySchema, connection is cached for + //! performance and can be cached since lifetime of the KDbOrderByColumn object depends on the + //! connection. @c nullptr if columnIndex is not provided. @since 3.2 + KDbConnection *connection = nullptr; + + //! Index of column to sort, -1 if field is present. @since 3.2 + int columnIndex; //! Value that indicates that column to sort (columnIndex) has been specified by providing its //! position, not name. For example, using "SELECT a, b FROM T ORDER BY 2". @@ -96,31 +121,29 @@ if (d->field) { return new KDbOrderByColumn(d->field, d->order); } - if (d->column) { + if (d->columnIndex >= 0) { KDbQueryColumnInfo* columnInfo; if (fromQuery && toQuery) { - int columnIndex = fromQuery->columnsOrder(conn).value(d->column); - if (columnIndex < 0) { - kdbWarning() << "Index not found for column" << *d->column; - return nullptr; - } - columnInfo = toQuery->expandedOrInternalField(conn, columnIndex); + columnInfo = toQuery->expandedOrInternalField(conn, d->columnIndex); if (!columnInfo) { - kdbWarning() << "Column info not found at index" << columnIndex << "in toQuery"; + kdbWarning() << "Column info not found at index" << d->columnIndex << "in toQuery"; return nullptr; } } else { - columnInfo = d->column; + columnInfo = column(); } return new KDbOrderByColumn(columnInfo, d->order, d->pos); } return nullptr; } KDbQueryColumnInfo* KDbOrderByColumn::column() const { - return d->column; + if (d->columnIndex < 0 || !d->querySchema || !d->connection) { + return nullptr; + } + return d->querySchema->expandedOrInternalField(d->connection, d->columnIndex); } int KDbOrderByColumn::position() const @@ -179,38 +202,67 @@ KDbEscapedString KDbOrderByColumn::toSqlString(bool includeTableName, KDbConnection *conn, + KDbQuerySchema *query, KDb::IdentifierEscapingType escapingType) const { const QByteArray orderString(d->order == KDbOrderByColumn::SortOrder::Ascending ? "" : " DESC"); KDbEscapedString fieldName, tableName, collationString; - if (d->column) { + KDbQueryColumnInfo *col = column(); + if (col) { if (d->pos > -1) return KDbEscapedString::number(d->pos + 1) + orderString; else { - if (includeTableName && d->column->alias().isEmpty()) { - tableName = KDbEscapedString(escapeIdentifier(d->column->field()->table()->name(), conn, escapingType)); + if (includeTableName && col->field()->table() && col->alias().isEmpty()) { + tableName = KDbEscapedString(escapeIdentifier(col->field()->table()->name(), conn, escapingType)); tableName += '.'; } - fieldName = KDbEscapedString(escapeIdentifier(d->column->aliasOrName(), conn, escapingType)); + fieldName = KDbEscapedString(escapeIdentifier(col->aliasOrName(), conn, escapingType)); } - if (d->column->field()->isTextType() && escapingType == KDb::DriverEscaping) { + if (conn && col->field()->isTextType() && escapingType == KDb::DriverEscaping) { collationString = conn->driver()->collationSql(); } } else { - if (d->field && includeTableName) { + QString aliasOrName; + if (includeTableName && d->field && d->field->table()) { tableName = KDbEscapedString(escapeIdentifier(d->field->table()->name(), conn, escapingType)); tableName += '.'; + } else if (d->field && conn && query) { + if (d->field->isExpression()) { + const int indexOfField = query->indexOf(*d->field); + aliasOrName = query->columnAlias(indexOfField); + if (aliasOrName.isEmpty()) { + kdbWarning() << "This field does not belong to specified query:" << *d->field + << endl << "cannot find alias"; + aliasOrName = QLatin1String("?unknown_field?"); + } + } else { + KDbQueryColumnInfo *ci = query->columnInfo(conn, d->field->name()); + if (ci) { + aliasOrName = ci->aliasOrName(); + } + } + } + if (aliasOrName.isEmpty()) { + // The field is not present on the SELECT list but is still correct, + // e.g. SELECT id FROM cars ORDER BY owner + aliasOrName = d->field ? d->field->name() : QLatin1String("?missing_field?")/*error*/; } - fieldName = KDbEscapedString(escapeIdentifier( - d->field ? d->field->name() : QLatin1String("??")/*error*/, conn, escapingType)); - if (d->field && d->field->isTextType() && escapingType == KDb::DriverEscaping) { + fieldName = KDbEscapedString(escapeIdentifier(aliasOrName, conn, escapingType)); + if (conn && d->field && d->field->isTextType() && escapingType == KDb::DriverEscaping) { collationString = conn->driver()->collationSql(); } } return tableName + fieldName + collationString + orderString; } +KDbEscapedString KDbOrderByColumn::toSqlString(bool includeTableName, + KDbConnection *conn, + KDb::IdentifierEscapingType escapingType) const +{ + return toSqlString(includeTableName, conn, nullptr, escapingType); +} + //======================================= class Q_DECL_HIDDEN KDbOrderByColumnList::Private @@ -395,17 +447,24 @@ } KDbEscapedString KDbOrderByColumnList::toSqlString(bool includeTableNames, KDbConnection *conn, - KDb::IdentifierEscapingType escapingType) const + KDbQuerySchema *query, + KDb::IdentifierEscapingType escapingType) const { KDbEscapedString string; for (QList::ConstIterator it(constBegin()); it != constEnd(); ++it) { if (!string.isEmpty()) string += ", "; - string += (*it)->toSqlString(includeTableNames, conn, escapingType); + string += (*it)->toSqlString(includeTableNames, conn, query, escapingType); } return string; } +KDbEscapedString KDbOrderByColumnList::toSqlString(bool includeTableNames, KDbConnection *conn, + KDb::IdentifierEscapingType escapingType) const +{ + return toSqlString(includeTableNames, conn, nullptr, escapingType); +} + void KDbOrderByColumnList::clear() { qDeleteAll(d->data); diff --git a/src/KDbQueryColumnInfo.h b/src/KDbQueryColumnInfo.h --- a/src/KDbQueryColumnInfo.h +++ b/src/KDbQueryColumnInfo.h @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2018 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 @@ -26,7 +26,9 @@ #include #include +class KDbConnection; class KDbField; +class KDbQuerySchema; //! @short Helper class that assigns additional information for the column in a query /*! The following information is assigned: @@ -89,7 +91,29 @@ //! @overload KDbQueryColumnInfo *foreignColumn(); const KDbQueryColumnInfo *foreignColumn() const; + /** + * Returns query schema for this column + * + * @since 3.2 + */ + const KDbQuerySchema* querySchema() const; + + /** + * Returns connection for this column + * + * @since 3.2 + */ + KDbConnection* connection(); + + /** + * @overload + * + * @since 3.2 + */ + const KDbConnection* connection() const; + private: + friend class KDbQuerySchema; class Private; Private * const d; Q_DISABLE_COPY(KDbQueryColumnInfo) diff --git a/src/KDbQueryColumnInfo.cpp b/src/KDbQueryColumnInfo.cpp --- a/src/KDbQueryColumnInfo.cpp +++ b/src/KDbQueryColumnInfo.cpp @@ -1,5 +1,5 @@ /* This file is part of the KDE project - Copyright (C) 2003-2016 Jarosław Staniek + Copyright (C) 2003-2018 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 @@ -18,38 +18,12 @@ */ #include "KDbQueryColumnInfo.h" +#include "KDbQuerySchema_p.h" #include "KDbTableSchema.h" #include "KDbField.h" #include "KDbField_p.h" #include "kdb_debug.h" -//! @internal -class Q_DECL_HIDDEN KDbQueryColumnInfo::Private -{ -public: - Private(KDbField *f, const QString& a, bool v, KDbQueryColumnInfo *foreign) - : field(f) - , alias(a) - , visible(v) - , indexForVisibleLookupValue(-1) - , foreignColumn(foreign) - { - } - - KDbField *field; - QString alias; - - //! @c true if this column is visible to the user (and its data is fetched by the engine) - bool visible; - - /*! Index of column with visible lookup value within the 'fields expanded' vector. - @see KDbQueryColumnInfo::indexForVisibleLookupValue() */ - int indexForVisibleLookupValue; - - //! Non-nullptr if this column is a visible column for @a foreignColumn - KDbQueryColumnInfo *foreignColumn; -}; - KDbQueryColumnInfo::KDbQueryColumnInfo(KDbField *f, const QString& alias, bool visible, KDbQueryColumnInfo *foreignColumn) : d(new Private(f, alias, visible, foreignColumn)) @@ -126,6 +100,21 @@ return d->foreignColumn; } +const KDbQuerySchema* KDbQueryColumnInfo::querySchema() const +{ + return d->querySchema; +} + +KDbConnection* KDbQueryColumnInfo::connection() +{ + return d->connection; +} + +const KDbConnection* KDbQueryColumnInfo::connection() const +{ + return d->connection; +} + QDebug operator<<(QDebug dbg, const KDbQueryColumnInfo& info) { QString fieldName; diff --git a/src/KDbQuerySchema.cpp b/src/KDbQuerySchema.cpp --- a/src/KDbQuerySchema.cpp +++ b/src/KDbQuerySchema.cpp @@ -749,6 +749,8 @@ if (!d->fakeRecordIdField) { d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), KDbField::BigInteger); d->fakeRecordIdCol = new KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true); + d->fakeRecordIdCol->d->querySchema = this; + d->fakeRecordIdCol->d->connection = conn; } tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + internalFieldCount] = d->fakeRecordIdCol; } @@ -821,6 +823,8 @@ foreach(KDbField *ast_f, *ast_fields) { KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); + ci->d->querySchema = this; + ci->d->connection = conn; list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; cache->columnsOrder.insert(ci, fieldPosition); @@ -835,6 +839,8 @@ // list.append(tab_f); KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, QString()/*no field for asterisk!*/, isColumnVisible(fieldPosition)); + ci->d->querySchema = this; + ci->d->connection = conn; list.append(ci); kdbDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; cache->columnsOrder.insert(ci, fieldPosition); @@ -844,6 +850,8 @@ } else { //a single field KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, columnAlias(fieldPosition), isColumnVisible(fieldPosition)); + ci->d->querySchema = this; + ci->d->connection = conn; list.append(ci); columnInfosOutsideAsterisks.insert(ci, true); kdbDebug() << "caching (unexpanded) column's order:" << *ci << "at position" << fieldPosition; @@ -883,8 +891,11 @@ cache->ownedVisibleFields.append(visibleColumn); // remember to delete later } - lookup_list.append( - new KDbQueryColumnInfo(visibleColumn, QString(), true/*visible*/, ci/*foreign*/)); + KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo( + visibleColumn, QString(), true /*visible*/, ci /*foreign*/); + lookupCi->d->querySchema = this; + lookupCi->d->connection = conn; + lookup_list.append(lookupCi); /* //add visibleField to the list of SELECTed fields if it is not yes present there if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { @@ -938,8 +949,11 @@ cache->ownedVisibleFields.append(visibleColumn); // remember to delete later } - lookup_list.append( - new KDbQueryColumnInfo(visibleColumn, QString(), true/*visible*/, ci/*foreign*/)); + KDbQueryColumnInfo *lookupCi = new KDbQueryColumnInfo( + visibleColumn, QString(), true /*visible*/, ci /*foreign*/); + lookupCi->d->querySchema = this; + lookupCi->d->connection = conn; + lookup_list.append(lookupCi); /* //add visibleField to the list of SELECTed fields if it is not yes present there if (!findTableField( visibleField->table()->name()+"."+visibleField->name() )) { diff --git a/src/KDbQuerySchema_p.h b/src/KDbQuerySchema_p.h --- a/src/KDbQuerySchema_p.h +++ b/src/KDbQuerySchema_p.h @@ -22,14 +22,42 @@ #include "KDbDriver.h" #include "KDbExpression.h" +#include "KDbQueryColumnInfo.h" #include "KDbQuerySchema.h" #include #include class KDbConnection; -//! @internal +class Q_DECL_HIDDEN KDbQueryColumnInfo::Private +{ +public: + Private(KDbField *f, const QString& a, bool v, KDbQueryColumnInfo *foreign) + : field(f) + , alias(a) + , visible(v) + , indexForVisibleLookupValue(-1) + , foreignColumn(foreign) + { + } + + KDbConnection *connection = nullptr; //!< Used to relate KDbQueryColumnInfo with query. @since 3.2 + const KDbQuerySchema *querySchema = nullptr; //!< Used to relate KDbQueryColumnInfo with query. @since 3.2 + KDbField *field; + QString alias; + + //! @c true if this column is visible to the user (and its data is fetched by the engine) + bool visible; + + /*! Index of column with visible lookup value within the 'fields expanded' vector. + @see KDbQueryColumnInfo::indexForVisibleLookupValue() */ + int indexForVisibleLookupValue; + + //! Non-nullptr if this column is a visible column for @a foreignColumn + KDbQueryColumnInfo *foreignColumn; +}; + class KDbQuerySchemaPrivate { Q_DECLARE_TR_FUNCTIONS(KDbQuerySchema)