diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index c95ded93..e81f7221 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,60 +1,63 @@ remove_definitions( -DQT_NO_KEYWORDS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_CAST_FROM_ASCII -DQT_USE_QSTRINGBUILDER ) set(FILES_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(KDB_LOCAL_PLUGINS_DIR ${PROJECT_BINARY_DIR}/plugins) add_definitions( -DFILES_OUTPUT_DIR=\"${FILES_OUTPUT_DIR}\" # make plugins work without installing them: -DKDB_LOCAL_PLUGINS_DIR=\"${KDB_LOCAL_PLUGINS_DIR}\" # nonstandard path for sqlite3 extensions, they would work too if we placed them # in ${KDB_LOCAL_PLUGINS_DIR}/sqlite3 but we want to test the "extraSqliteExtensionPaths" # connection option in ConnectionTest::testCreateDb(): -DSQLITE_LOCAL_ICU_EXTENSION_PATH=\"${KDB_LOCAL_PLUGINS_DIR}/sqlite3\" -DYYTOKENTYPE # this removes access to yytokentype enum as we should access KDb::Token instead ) include(ECMAddTests) find_package(Qt5Test) set_package_properties(Qt5Test PROPERTIES DESCRIPTION "Qt5Test library" URL "https://www.qt.io" TYPE RECOMMENDED PURPOSE "Required by tests") # A helper library for db-related tests add_library(kdbtestutils SHARED KDbTestUtils.cpp ) target_link_libraries(kdbtestutils PUBLIC KDb Qt5::Test ) generate_export_header(kdbtestutils) # Tests ecm_add_tests( ConnectionOptionsTest.cpp ConnectionTest.cpp DateTimeTest.cpp DriverTest.cpp ExpressionsTest.cpp MissingTableTest.cpp + OrderByColumnTest.cpp QuerySchemaTest.cpp KDbTest.cpp LINK_LIBRARIES kdbtestutils ) 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() add_subdirectory(tools) add_subdirectory(parser) diff --git a/autotests/OrderByColumnTest.cpp b/autotests/OrderByColumnTest.cpp new file mode 100644 index 00000000..d0b7e053 --- /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/autotests/OrderByColumnTest.h b/autotests/OrderByColumnTest.h new file mode 100644 index 00000000..2b226b16 --- /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/src/KDbNativeStatementBuilder.cpp b/src/KDbNativeStatementBuilder.cpp index e04b23d0..e1d82961 100644 --- a/src/KDbNativeStatementBuilder.cpp +++ b/src/KDbNativeStatementBuilder.cpp @@ -1,533 +1,533 @@ /* This file is part of the KDE project Copyright (C) 2003-2017 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 "KDbNativeStatementBuilder.h" #include "KDbConnection.h" #include "kdb_debug.h" #include "KDbDriverBehavior.h" #include "KDbDriver_p.h" #include "KDbExpression.h" #include "KDbLookupFieldSchema.h" #include "KDbOrderByColumn.h" #include "KDbQueryAsterisk.h" #include "KDbQuerySchema.h" #include "KDbQuerySchemaParameter.h" #include "KDbRelationship.h" KDbSelectStatementOptions::~KDbSelectStatementOptions() { } //================================================ class Q_DECL_HIDDEN KDbNativeStatementBuilder::Private { public: Private() {} //! @todo use equivalent of QPointer KDbConnection *connection; KDb::IdentifierEscapingType dialect; private: Q_DISABLE_COPY(Private) }; //================================================ KDbNativeStatementBuilder::KDbNativeStatementBuilder(KDbConnection *connection, KDb::IdentifierEscapingType dialect) : d(new Private) { d->connection = connection; d->dialect = dialect; } KDbNativeStatementBuilder::~KDbNativeStatementBuilder() { delete d; } static bool selectStatementInternal(KDbEscapedString *target, KDbConnection *connection, KDb::IdentifierEscapingType dialect, KDbQuerySchema* querySchema, const KDbSelectStatementOptions& options, const QList& parameters) { Q_ASSERT(target); Q_ASSERT(querySchema); //"SELECT FROM ..." is theoretically allowed " //if (querySchema.fieldCount()<1) // return QString(); // Each SQL identifier needs to be escaped in the generated query. const KDbDriver *driver = dialect == KDb::DriverEscaping ? connection->driver() : nullptr; if (!querySchema->statement().isEmpty()) { //! @todo replace with KDbNativeQuerySchema? It shouldn't be here. *target = querySchema->statement(); return true; } //! @todo looking at singleTable is visually nice but a field name can conflict //! with function or variable name... int number = 0; QList* tables = querySchema->tables(); bool singleTable = tables->count() <= 1; if (singleTable) { //make sure we will have single table: foreach(KDbField *f, *querySchema->fields()) { if (querySchema->isColumnVisible(number) && f->table() && f->table()->lookupFieldSchema(*f)) { //uups, no, there's at least one left join singleTable = false; break; } number++; } } KDbEscapedString sql; //final sql string sql.reserve(4096); KDbEscapedString s_additional_joins; //additional joins needed for lookup fields KDbEscapedString s_additional_fields; //additional fields to append to the fields list int internalUniqueTableAliasNumber = 0; //used to build internalUniqueTableAliases int internalUniqueQueryAliasNumber = 0; //used to build internalUniqueQueryAliases number = 0; QList subqueries_for_lookup_data; // subqueries will be added to FROM section const QString kdb_subquery_prefix = QStringLiteral("__kdb_subquery_"); KDbQuerySchemaParameterValueListIterator paramValuesIt(parameters); KDbQuerySchemaParameterValueListIterator *paramValuesItPtr = parameters.isEmpty() ? nullptr : ¶mValuesIt; foreach(KDbField *f, *querySchema->fields()) { if (querySchema->isColumnVisible(number)) { if (!sql.isEmpty()) sql += ", "; if (f->isQueryAsterisk()) { KDbQueryAsterisk *asterisk = static_cast(f); if (!singleTable && asterisk->isSingleTableAsterisk()) { //single-table * sql.append(KDb::escapeIdentifier(driver, asterisk->table()->name())).append(".*"); } else { /* All-tables asterisk NOTE: do not output in this form because there can be extra tables automatically added for obtaining lookup data what changes number of fields. Reliable solution to that: for tables T1..Tn output T1.*,..Tn.* Example for Northwind: - instead of: SELECT * FROM orders LEFT OUTER JOIN customers ON orders.customerid=customers.customerid - use this: SELECT orders.*, customers.contactname FROM orders LEFT OUTER JOIN customers ON orders.customerid=customers.customerid */ KDbEscapedString s_tables; for (KDbTableSchema *table : *tables) { if (!s_tables.isEmpty()) { s_tables += ", "; } s_tables.append(KDb::escapeIdentifier(driver, table->name()) + QLatin1String(".*")); } sql += s_tables; } } else { if (f->isExpression()) { sql += f->expression().toString(driver, paramValuesItPtr); } else { if (!f->table()) {//sanity check return false; } QString tableName; int tablePosition = querySchema->tableBoundToColumn(number); if (tablePosition >= 0) { tableName = KDb::iifNotEmpty(querySchema->tableAlias(tablePosition), f->table()->name()); } if (options.addVisibleLookupColumns()) { // try to find table/alias name harder if (tableName.isEmpty()) { tableName = querySchema->tableAlias(f->table()->name()); } if (tableName.isEmpty()) { tableName = f->table()->name(); } } if (!singleTable && !tableName.isEmpty()) { sql.append(KDb::escapeIdentifier(driver, tableName)).append('.'); } sql += KDb::escapeIdentifier(driver, f->name()); } const QString aliasString(querySchema->columnAlias(number)); if (!aliasString.isEmpty()) { sql.append(" AS ").append(KDb::escapeIdentifier(driver, aliasString)); } //! @todo add option that allows to omit "AS" keyword } KDbLookupFieldSchema *lookupFieldSchema = (options.addVisibleLookupColumns() && f->table()) ? f->table()->lookupFieldSchema(*f) : nullptr; if (lookupFieldSchema && lookupFieldSchema->boundColumn() >= 0) { // 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" KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) { KDbTableSchema *lookupTable = connection->tableSchema(recordSource.name()); KDbFieldList* visibleColumns = nullptr; KDbField *boundField = nullptr; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns())) && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) { //add LEFT OUTER JOIN if (!s_additional_joins.isEmpty()) s_additional_joins += ' '; const QString internalUniqueTableAlias( QLatin1String("__kdb_") + lookupTable->name() + QLatin1Char('_') + QString::number(internalUniqueTableAliasNumber++)); s_additional_joins += KDbEscapedString("LEFT OUTER JOIN %1 AS %2 ON %3.%4=%5.%6") .arg(KDb::escapeIdentifier(driver, lookupTable->name())) .arg(KDb::escapeIdentifier(driver, internalUniqueTableAlias)) .arg(KDb::escapeIdentifier(driver, querySchema->tableAliasOrName(f->table()->name()))) .arg(KDb::escapeIdentifier(driver, f->name())) .arg(KDb::escapeIdentifier(driver, internalUniqueTableAlias)) .arg(KDb::escapeIdentifier(driver, boundField->name())); //add visibleField to the list of SELECTed fields //if it is not yet present there if (!s_additional_fields.isEmpty()) s_additional_fields += ", "; //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" //! @todo Add possibility for joining the values at client side. s_additional_fields += visibleColumns->sqlFieldsList( connection, QLatin1String(" || ' ' || "), internalUniqueTableAlias, dialect); } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) { KDbQuerySchema *lookupQuery = connection->querySchema(recordSource.name()); if (!lookupQuery) { kdbWarning() << "!lookupQuery"; return false; } const KDbQueryColumnInfo::Vector fieldsExpanded( lookupQuery->fieldsExpanded(connection)); if (lookupFieldSchema->boundColumn() >= fieldsExpanded.count()) { kdbWarning() << "lookupFieldSchema->boundColumn() >= fieldsExpanded.count()"; return false; } KDbQueryColumnInfo *boundColumnInfo = fieldsExpanded.at(lookupFieldSchema->boundColumn()); if (!boundColumnInfo) { kdbWarning() << "!boundColumnInfo"; return false; } KDbField *boundField = boundColumnInfo->field(); if (!boundField) { kdbWarning() << "!boundField"; return false; } //add LEFT OUTER JOIN if (!s_additional_joins.isEmpty()) s_additional_joins += ' '; KDbEscapedString internalUniqueQueryAlias(KDb::escapeIdentifier( driver, kdb_subquery_prefix + lookupQuery->name() + QLatin1Char('_') + QString::number(internalUniqueQueryAliasNumber++))); KDbNativeStatementBuilder builder(connection, dialect); KDbEscapedString subSql; if (!builder.generateSelectStatement(&subSql, lookupQuery, options, parameters)) { return false; } s_additional_joins += KDbEscapedString("LEFT OUTER JOIN (%1) AS %2 ON %3.%4=%5.%6") .arg(subSql) .arg(internalUniqueQueryAlias) .arg(KDb::escapeIdentifier(driver, f->table()->name())) .arg(KDb::escapeIdentifier(driver, f->name())) .arg(internalUniqueQueryAlias) .arg(KDb::escapeIdentifier(driver, boundColumnInfo->aliasOrName())); if (!s_additional_fields.isEmpty()) s_additional_fields += ", "; const QList visibleColumns(lookupFieldSchema->visibleColumns()); KDbEscapedString expression; foreach(int visibleColumnIndex, visibleColumns) { //! @todo Add lookup schema option for separator other than ' ' or even option for placeholders like "Name ? ?" //! @todo Add possibility for joining the values at client side. if (fieldsExpanded.count() <= visibleColumnIndex) { kdbWarning() << "fieldsExpanded.count() <= (*visibleColumnsIt) : " << fieldsExpanded.count() << " <= " << visibleColumnIndex; return false; } if (!expression.isEmpty()) expression += " || ' ' || "; expression += ( internalUniqueQueryAlias + '.' + KDb::escapeIdentifier(driver, fieldsExpanded.value(visibleColumnIndex)->aliasOrName()) ); } s_additional_fields += expression; } else { kdbWarning() << "unsupported record source type" << recordSource.typeName(); return false; } } } number++; } //add lookup fields if (!s_additional_fields.isEmpty()) sql += (", " + s_additional_fields); if (driver && options.alsoRetrieveRecordId()) { //append rowid column //! @todo Check if the rowid isn't already part of regular SELECT columns, if so, don't add KDbEscapedString s; if (!sql.isEmpty()) s = ", "; if (querySchema->masterTable()) s += KDbEscapedString(querySchema->tableAliasOrName(querySchema->masterTable()->name())) + '.'; s += KDbDriverPrivate::behavior(driver)->ROW_ID_FIELD_NAME; sql += s; } if (sql.isEmpty()) { sql.prepend("SELECT"); // "SELECT FROM ..." case } else { sql.prepend("SELECT "); } if (!tables->isEmpty() || !subqueries_for_lookup_data.isEmpty()) { sql += " FROM "; KDbEscapedString s_from; number = 0; foreach(KDbTableSchema *table, *tables) { if (!s_from.isEmpty()) s_from += ", "; s_from += KDb::escapeIdentifier(driver, table->name()); const QString aliasString(querySchema->tableAlias(number)); if (!aliasString.isEmpty()) s_from.append(" AS ").append(KDb::escapeIdentifier(driver, aliasString)); number++; } // add subqueries for lookup data int subqueries_for_lookup_data_counter = 0; foreach(KDbQuerySchema* subQuery, subqueries_for_lookup_data) { if (!s_from.isEmpty()) s_from += ", "; KDbEscapedString subSql; if (!selectStatementInternal(&subSql, connection, dialect, subQuery, options, parameters)) { return false; } s_from += '(' + subSql + ") AS " + KDb::escapeIdentifier( driver, kdb_subquery_prefix + QString::number(subqueries_for_lookup_data_counter++)); } sql += s_from; } KDbEscapedString s_where; s_where.reserve(4096); //JOINS if (!s_additional_joins.isEmpty()) { sql += ' ' + s_additional_joins + ' '; } //! @todo: we're using WHERE for joins now; use INNER/LEFT/RIGHT JOIN later //WHERE bool wasWhere = false; //for later use foreach(KDbRelationship *rel, *querySchema->relationships()) { if (s_where.isEmpty()) { wasWhere = true; } else s_where += " AND "; KDbEscapedString s_where_sub; foreach(const KDbField::Pair &pair, *rel->fieldPairs()) { if (!s_where_sub.isEmpty()) s_where_sub += " AND "; s_where_sub += KDbEscapedString(KDb::escapeIdentifier(driver, pair.first->table()->name())) + '.' + KDb::escapeIdentifier(driver, pair.first->name()) + " = " + KDb::escapeIdentifier(driver, pair.second->table()->name()) + '.' + KDb::escapeIdentifier(driver, pair.second->name()); } if (rel->fieldPairs()->count() > 1) { s_where_sub.prepend('('); s_where_sub += ')'; } s_where += s_where_sub; } //EXPLICITLY SPECIFIED WHERE EXPRESSION if (!querySchema->whereExpression().isNull()) { if (wasWhere) { //! @todo () are not always needed s_where = '(' + s_where + ") AND (" + querySchema->whereExpression().toString(driver, paramValuesItPtr) + ')'; } else { s_where = querySchema->whereExpression().toString(driver, paramValuesItPtr); } } if (!s_where.isEmpty()) sql += " WHERE " + s_where; //! @todo (js) add other sql parts //(use wasWhere here) // 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 // (especially helps when there are complex JOINs) KDbOrderByColumnList automaticPKOrderBy; const KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded(connection)); foreach(int pkeyFieldsIndex, pkeyFieldsOrder) { if (pkeyFieldsIndex < 0) // no field mentioned in this query continue; if (pkeyFieldsIndex >= fieldsExpanded.count()) { kdbWarning() << "ORDER BY: (*it) >= fieldsExpanded.count() - " << pkeyFieldsIndex << " >= " << fieldsExpanded.count(); continue; } KDbQueryColumnInfo *ci = fieldsExpanded[ pkeyFieldsIndex ]; automaticPKOrderBy.appendColumn(ci); } orderByString = automaticPKOrderBy.toSqlString(!singleTable /*includeTableName*/, - connection, dialect); + connection, querySchema, dialect); } if (!orderByString.isEmpty()) sql += (" ORDER BY " + orderByString); //kdbDebug() << sql; *target = sql; return true; } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbQuerySchema* querySchema, const KDbSelectStatementOptions& options, const QList& parameters) const { return selectStatementInternal(target, d->connection, d->dialect, querySchema, options, parameters); } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbQuerySchema* querySchema, const QList& parameters) const { return selectStatementInternal(target, d->connection, d->dialect, querySchema, KDbSelectStatementOptions(), parameters); } bool KDbNativeStatementBuilder::generateSelectStatement(KDbEscapedString *target, KDbTableSchema* tableSchema, const KDbSelectStatementOptions& options) const { return generateSelectStatement(target, tableSchema->query(), options); } bool KDbNativeStatementBuilder::generateCreateTableStatement(KDbEscapedString *target, const KDbTableSchema& tableSchema) const { if (!target) { return false; } // Each SQL identifier needs to be escaped in the generated query. const KDbDriver *driver = d->dialect == KDb::DriverEscaping ? d->connection->driver() : nullptr; KDbEscapedString sql; sql.reserve(4096); sql = KDbEscapedString("CREATE TABLE ") + KDb::escapeIdentifier(driver, tableSchema.name()) + " ("; bool first = true; for (const KDbField *field : *tableSchema.fields()) { if (first) first = false; else sql += ", "; KDbEscapedString v = KDbEscapedString(KDb::escapeIdentifier(driver, field->name())) + ' '; const bool autoinc = field->isAutoIncrement(); const bool pk = field->isPrimaryKey() || (autoinc && driver && driver->behavior()->AUTO_INCREMENT_REQUIRES_PK); //! @todo warning: ^^^^^ this allows only one autonumber per table when AUTO_INCREMENT_REQUIRES_PK==true! const KDbField::Type type = field->type(); // cache: evaluating type of expressions can be expensive if (autoinc && d->connection->driver()->behavior()->SPECIAL_AUTO_INCREMENT_DEF) { if (pk) v.append(d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE).append(' ') .append(d->connection->driver()->behavior()->AUTO_INCREMENT_PK_FIELD_OPTION); else v.append(d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE).append(' ') .append(d->connection->driver()->behavior()->AUTO_INCREMENT_FIELD_OPTION); } else { if (autoinc && !d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE.isEmpty()) v += d->connection->driver()->behavior()->AUTO_INCREMENT_TYPE; else v += d->connection->driver()->sqlTypeName(type, *field); if (KDbField::isIntegerType(type) && field->isUnsigned()) { v.append(' ').append(d->connection->driver()->behavior()->UNSIGNED_TYPE_KEYWORD); } if (KDbField::isFPNumericType(type) && field->precision() > 0) { if (field->scale() > 0) v += QString::fromLatin1("(%1,%2)").arg(field->precision()).arg(field->scale()); else v += QString::fromLatin1("(%1)").arg(field->precision()); } else if (type == KDbField::Text) { int realMaxLen; if (d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH == 0) { realMaxLen = field->maxLength(); // allow to skip (N) } else { // max length specified by driver if (field->maxLength() == 0) { // as long as possible realMaxLen = d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH; } else { // not longer than specified by driver realMaxLen = qMin(d->connection->driver()->behavior()->TEXT_TYPE_MAX_LENGTH, field->maxLength()); } } if (realMaxLen > 0) { v += QString::fromLatin1("(%1)").arg(realMaxLen); } } if (autoinc) { v.append(' ').append(pk ? d->connection->driver()->behavior()->AUTO_INCREMENT_PK_FIELD_OPTION : d->connection->driver()->behavior()->AUTO_INCREMENT_FIELD_OPTION); } else { //! @todo here is automatically a single-field key created if (pk) v += " PRIMARY KEY"; } if (!pk && field->isUniqueKey()) v += " UNIQUE"; ///@todo IS this ok for all engines?: if (!autoinc && !field->isPrimaryKey() && field->isNotNull()) if (!autoinc && !pk && field->isNotNull()) v += " NOT NULL"; //only add not null option if no autocommit is set if (d->connection->driver()->supportsDefaultValue(*field) && field->defaultValue().isValid()) { KDbEscapedString valToSql(d->connection->driver()->valueToSql(field, field->defaultValue())); if (!valToSql.isEmpty()) //for sanity v += " DEFAULT " + valToSql; } } sql += v; } sql += ')'; *target = sql; return true; } diff --git a/src/KDbOrderByColumn.cpp b/src/KDbOrderByColumn.cpp index 6986cd95..4155db7a 100644 --- a/src/KDbOrderByColumn.cpp +++ b/src/KDbOrderByColumn.cpp @@ -1,413 +1,472 @@ /* 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 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 "KDbOrderByColumn.h" #include "KDbQuerySchema.h" #include "KDbQuerySchema_p.h" #include "KDbConnection.h" #include "kdb_debug.h" class Q_DECL_HIDDEN KDbOrderByColumn::Private { 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". //! Value of -1 means that the column to sort has been specified by providing its name (or alias). //! For example "SELECT a, b FROM T ORDER BY b". -1 is the default. int pos; //! Used only in case when the second constructor is used. KDbField* field; //! Sort order KDbOrderByColumn::SortOrder order; }; //---- KDbOrderByColumn::KDbOrderByColumn() : d(new Private) { } KDbOrderByColumn::KDbOrderByColumn(KDbQueryColumnInfo* column, SortOrder order, int pos) : d(new Private(column, pos, nullptr, order)) { } KDbOrderByColumn::KDbOrderByColumn(KDbField* field, SortOrder order) : d(new Private(nullptr, -1, field, order)) { } KDbOrderByColumn::KDbOrderByColumn(const KDbOrderByColumn &other) : d(new Private(*other.d)) { } KDbOrderByColumn::~KDbOrderByColumn() { delete d; } KDbOrderByColumn *KDbOrderByColumn::copy(KDbConnection *conn, KDbQuerySchema *fromQuery, KDbQuerySchema *toQuery) const { 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 { return d->pos; } KDbField* KDbOrderByColumn::field() const { return d->field; } KDbOrderByColumn::SortOrder KDbOrderByColumn::sortOrder() const { return d->order; } KDbOrderByColumn& KDbOrderByColumn::operator=(const KDbOrderByColumn &other) { if (this != &other) { *d = *other.d; } return *this; } bool KDbOrderByColumn::operator==(const KDbOrderByColumn& col) const { return *d == *col.d; } QDebug operator<<(QDebug dbg, const KDbOrderByColumn& order) { const QLatin1String orderString( order.sortOrder() == KDbOrderByColumn::SortOrder::Ascending ? "ASCENDING" : "DESCENDING"); if (order.column()) { if (order.position() > -1) { dbg.nospace() << qPrintable(QString::fromLatin1("COLUMN_AT_POSITION_%1(").arg(order.position() + 1)) << *order.column() << ',' << qPrintable(orderString) << ')'; return dbg.space(); } else { dbg.nospace() << "COLUMN(" << *order.column() << ','; dbg.nospace() << qPrintable(orderString) << ')'; return dbg.space(); } } if (order.field()) { dbg.nospace() << "FIELD(" << *order.field() << ','; dbg.nospace() << qPrintable(orderString) << ')'; return dbg.space(); } dbg.nospace() << "NONE"; return dbg.space(); } 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 { public: Private() { } ~Private() { qDeleteAll(data); } QList data; }; KDbOrderByColumnList::KDbOrderByColumnList() : d(new Private) { } KDbOrderByColumnList::KDbOrderByColumnList(const KDbOrderByColumnList& other, KDbConnection *conn, KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery) : KDbOrderByColumnList() { for (QList::ConstIterator it(other.constBegin()); it != other.constEnd(); ++it) { KDbOrderByColumn* order = (*it)->copy(conn, fromQuery, toQuery); if (order) { d->data.append(order); } } } KDbOrderByColumnList::~KDbOrderByColumnList() { delete d; } bool KDbOrderByColumnList::operator==(const KDbOrderByColumnList &other) const { return d->data == other.d->data; } const KDbOrderByColumn* KDbOrderByColumnList::value(int index) const { return d->data.value(index); } KDbOrderByColumn* KDbOrderByColumnList::value(int index) { return d->data.value(index); } bool KDbOrderByColumnList::appendFields(KDbConnection *conn, KDbQuerySchema* querySchema, const QString& field1, KDbOrderByColumn::SortOrder order1, const QString& field2, KDbOrderByColumn::SortOrder order2, const QString& field3, KDbOrderByColumn::SortOrder order3, const QString& field4, KDbOrderByColumn::SortOrder order4, const QString& field5, KDbOrderByColumn::SortOrder order5) { if (!querySchema) { return false; } int numAdded = 0; #define ADD_COL(fieldName, order) \ if (ok && !fieldName.isEmpty()) { \ if (!appendField(conn, querySchema, fieldName, order)) \ ok = false; \ else \ numAdded++; \ } bool ok = true; ADD_COL(field1, order1) ADD_COL(field2, order2) ADD_COL(field3, order3) ADD_COL(field4, order4) ADD_COL(field5, order5) #undef ADD_COL if (ok) { return true; } for (int i = 0; i < numAdded; i++) { d->data.removeLast(); } return false; } void KDbOrderByColumnList::appendColumn(KDbQueryColumnInfo* columnInfo, KDbOrderByColumn::SortOrder order) { if (columnInfo) { d->data.append(new KDbOrderByColumn(columnInfo, order)); } } bool KDbOrderByColumnList::appendColumn(KDbConnection *conn, KDbQuerySchema* querySchema, KDbOrderByColumn::SortOrder order, int pos) { if (!querySchema) { return false; } const KDbQueryColumnInfo::Vector fieldsExpanded(querySchema->fieldsExpanded(conn)); if (pos < 0 || pos >= fieldsExpanded.size()) { return false; } KDbQueryColumnInfo* ci = fieldsExpanded[pos]; d->data.append(new KDbOrderByColumn(ci, order, pos)); return true; } void KDbOrderByColumnList::appendField(KDbField* field, KDbOrderByColumn::SortOrder order) { if (field) { d->data.append(new KDbOrderByColumn(field, order)); } } bool KDbOrderByColumnList::appendField(KDbConnection *conn, KDbQuerySchema* querySchema, const QString& fieldName, KDbOrderByColumn::SortOrder order) { if (!querySchema) { return false; } KDbQueryColumnInfo *columnInfo = querySchema->columnInfo(conn, fieldName); if (columnInfo) { d->data.append(new KDbOrderByColumn(columnInfo, order)); return true; } KDbField *field = querySchema->findTableField(fieldName); if (field) { d->data.append(new KDbOrderByColumn(field, order)); return true; } kdbWarning() << "no such field" << fieldName; return false; } bool KDbOrderByColumnList::isEmpty() const { return d->data.isEmpty(); } int KDbOrderByColumnList::count() const { return d->data.count(); } QList::Iterator KDbOrderByColumnList::begin() { return d->data.begin(); } QList::Iterator KDbOrderByColumnList::end() { return d->data.end(); } QList::ConstIterator KDbOrderByColumnList::constBegin() const { return d->data.constBegin(); } QList::ConstIterator KDbOrderByColumnList::constEnd() const { return d->data.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 + 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); d->data.clear(); } diff --git a/src/KDbOrderByColumn.h b/src/KDbOrderByColumn.h index 47408e0c..96c6cdb6 100644 --- a/src/KDbOrderByColumn.h +++ b/src/KDbOrderByColumn.h @@ -1,225 +1,252 @@ /* 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 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_ORDERBYCOLUMN_H #define KDB_ORDERBYCOLUMN_H #include "KDbEscapedString.h" #include "KDbGlobal.h" class KDbConnection; class KDbField; class KDbQueryColumnInfo; class KDbQuerySchema; //! @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: //! Column sort order //! @since 3.1 enum class SortOrder { Ascending = Qt::AscendingOrder, Descending = Qt::DescendingOrder }; //! Creates an empty information about a single query column. KDbOrderByColumn(); //! Creates a copy of the @a other //! @since 3.1 KDbOrderByColumn(const KDbOrderByColumn &other); //! Creates information about a single query column @a column used for sorting. explicit KDbOrderByColumn(KDbQueryColumnInfo* column, SortOrder order = SortOrder::Ascending, 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)). explicit KDbOrderByColumn(KDbField* field, SortOrder order = SortOrder::Ascending); ~KDbOrderByColumn(); /*! @return copy of this KDbOrderByColumn object. @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(KDbConnection *conn, KDbQuerySchema *fromQuery, KDbQuerySchema *toQuery) const Q_REQUIRED_RESULT; //! 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 sort order for the column KDbOrderByColumn::SortOrder sortOrder() const; //! Assigns @a other to this object returns a reference to this object. //! @since 3.1 KDbOrderByColumn& operator=(const KDbOrderByColumn &other); //! @return true if this column is the same as @a col bool operator==(const KDbOrderByColumn& col) const; //! @return @c true if this object is not equal to @a other; otherwise returns @c false. //! @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; //! Converts @a order to Qt::SortOrder type inline static Qt::SortOrder toQt(SortOrder order) { return static_cast(order); } //! Converts @a order to SortOrder type inline static SortOrder fromQt(Qt::SortOrder order) { return static_cast(order); } private: class Private; Private * const d; }; //! @short KDbOrderByColumnList provides list of sorted columns for a query schema class KDB_EXPORT KDbOrderByColumnList { public: /*! Constructs empty list of ordered columns. */ KDbOrderByColumnList(); /*! A copy constructor. */ KDbOrderByColumnList(const KDbOrderByColumnList& other, KDbConnection *conn, KDbQuerySchema* fromQuery, KDbQuerySchema* toQuery); ~KDbOrderByColumnList(); //! @return @c true if this object is equal to @a other; otherwise returns @c false. //! @since 3.1 bool operator==(const KDbOrderByColumnList &other) const; //! @return @c true if this object is not equal to @a other; otherwise returns @c false. //! @since 3.1 inline bool operator!=(const KDbOrderByColumnList &other) const { return !operator==(other); } //! Returns column with given index. //! @since 3.1 const KDbOrderByColumn* value(int index) const; //! @overload KDbOrderByColumn* value(int index); /*! 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) Returns @c false if @a querySchema is @c nullptr. */ bool appendFields(KDbConnection *conn, KDbQuerySchema* querySchema, const QString& field1, KDbOrderByColumn::SortOrder order1 = KDbOrderByColumn::SortOrder::Ascending, const QString& field2 = QString(), KDbOrderByColumn::SortOrder order2 = KDbOrderByColumn::SortOrder::Ascending, const QString& field3 = QString(), KDbOrderByColumn::SortOrder order3 = KDbOrderByColumn::SortOrder::Ascending, const QString& field4 = QString(), KDbOrderByColumn::SortOrder order4 = KDbOrderByColumn::SortOrder::Ascending, const QString& field5 = QString(), KDbOrderByColumn::SortOrder order5 = KDbOrderByColumn::SortOrder::Ascending); /*! Appends column @a columnInfo. Does nothing if @a columnInfo is @c nullptr. */ void appendColumn(KDbQueryColumnInfo* columnInfo, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending); /*! Appends a field @a field. Read documentation of @ref KDbOrderByColumn(KDbField* field, SortOrder order) for more info. Does nothing if @a field is @c nullptr. */ void appendField(KDbField* field, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending); /*! Appends field with a name @a field. @return @c true on successful appending, and @c false if there is no such field or alias name in the @a querySchema. Returns @c false if @a querySchema is @c nullptr. */ bool appendField(KDbConnection *conn, KDbQuerySchema* querySchema, const QString& fieldName, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending); /*! 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. Returns @c false if @a querySchema is @c nullptr. */ bool appendColumn(KDbConnection *conn, KDbQuerySchema* querySchema, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending, 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(); /*! Returns an STL-style iterator pointing to the first column in the list. */ QList::Iterator begin(); /*! Returns an STL-style iterator pointing to the imaginary item after the last column * in the list. */ QList::Iterator end(); /*! Returns an const STL-style iterator pointing to the first column in the list. */ QList::ConstIterator constBegin() const; /*! Returns a const STL-style iterator pointing to the imaginary item after the last column * in the list. */ 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; Q_DISABLE_COPY(KDbOrderByColumnList) }; //! 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); #endif diff --git a/src/KDbQueryColumnInfo.cpp b/src/KDbQueryColumnInfo.cpp index 123ea45b..e00afa4f 100644 --- a/src/KDbQueryColumnInfo.cpp +++ b/src/KDbQueryColumnInfo.cpp @@ -1,145 +1,134 @@ /* 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 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 "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)) { } KDbQueryColumnInfo::~KDbQueryColumnInfo() { delete d; } KDbField* KDbQueryColumnInfo::field() { return d->field; } const KDbField* KDbQueryColumnInfo::field() const { return d->field; } void KDbQueryColumnInfo::setField(KDbField *field) { d->field = field; } QString KDbQueryColumnInfo::alias() const { return d->alias; } void KDbQueryColumnInfo::setAlias(const QString &alias) { d->alias = alias; } QString KDbQueryColumnInfo::aliasOrName() const { return d->alias.isEmpty() ? d->field->name() : d->alias; } QString KDbQueryColumnInfo::captionOrAliasOrName() const { return d->field->caption().isEmpty() ? aliasOrName() : d->field->caption(); } bool KDbQueryColumnInfo::isVisible() const { return d->visible; } void KDbQueryColumnInfo::setVisible(bool set) { d->visible = set; } int KDbQueryColumnInfo::indexForVisibleLookupValue() const { return d->indexForVisibleLookupValue; } void KDbQueryColumnInfo::setIndexForVisibleLookupValue(int index) { d->indexForVisibleLookupValue = index; } KDbQueryColumnInfo *KDbQueryColumnInfo::foreignColumn() { return d->foreignColumn; } const KDbQueryColumnInfo *KDbQueryColumnInfo::foreignColumn() const { 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; if (info.field()->name().isEmpty()) { fieldName = QLatin1String(""); } else { fieldName = info.field()->name(); } dbg.nospace() << (info.field()->table() ? (info.field()->table()->name() + QLatin1Char('.')) : QString()) + fieldName; debug(dbg, *info.field(), KDbFieldDebugNoOptions); dbg.nospace() << qPrintable(info.alias().isEmpty() ? QString() : (QLatin1String(" AS ") + info.alias())) << qPrintable(QLatin1String(info.isVisible() ? nullptr : " [INVISIBLE]")); return dbg.space(); } diff --git a/src/KDbQueryColumnInfo.h b/src/KDbQueryColumnInfo.h index c84b71c3..3a0fcfd1 100644 --- a/src/KDbQueryColumnInfo.h +++ b/src/KDbQueryColumnInfo.h @@ -1,101 +1,125 @@ /* 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 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_QUERYCOLUMNINFO_H #define KDB_QUERYCOLUMNINFO_H #include "kdb_export.h" #include #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: - alias - visibility KDbQueryColumnInfo::Vector is created and returned by KDbQuerySchema::fieldsExpanded(). It is efficiently cached within the KDbQuerySchema object. */ class KDB_EXPORT KDbQueryColumnInfo { public: typedef QVector Vector; typedef QList List; typedef QList::ConstIterator ListIterator; KDbQueryColumnInfo(KDbField *f, const QString &alias, bool visible, KDbQueryColumnInfo *foreignColumn = nullptr); ~KDbQueryColumnInfo(); //! @return field for this column KDbField *field(); //! @overload KDbField *field() const KDbField *field() const; //! Sets the field void setField(KDbField *field); //! @return alias for this column QString alias() const; //! Sets the alias void setAlias(const QString &alias); //! @return alias if it is not empty, field's name otherwise. QString aliasOrName() const; //! @return field's caption if it is not empty, field's alias otherwise. //! If alias is also empty - returns field's name. QString captionOrAliasOrName() const; //! @return true is this column is visible bool isVisible() const; //! Sets the visible flag void setVisible(bool set); /*! @return index of column with visible lookup value within the 'fields expanded' vector. -1 means no visible lookup value is available because there is no lookup for the column defined. Cached for efficiency as we use this information frequently. @see KDbLookupFieldSchema::visibleVolumn() */ int indexForVisibleLookupValue() const; /*! Sets index of column with visible lookup value within the 'fields expanded' vector. */ void setIndexForVisibleLookupValue(int index); //! @return non-nullptr if this column is a visible column for other column KDbQueryColumnInfo *foreignColumn(); //! @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) }; //! Sends information about column info @a info to debug output @a dbg. KDB_EXPORT QDebug operator<<(QDebug dbg, const KDbQueryColumnInfo& info); #endif diff --git a/src/KDbQuerySchema.cpp b/src/KDbQuerySchema.cpp index 9d8e1d3d..f5c9e2a1 100644 --- a/src/KDbQuerySchema.cpp +++ b/src/KDbQuerySchema.cpp @@ -1,1395 +1,1409 @@ /* This file is part of the KDE project 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 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 "KDbQueryAsterisk.h" #include "KDbConnection.h" #include "KDbConnection_p.h" #include "kdb_debug.h" #include "KDbLookupFieldSchema.h" #include "KDbOrderByColumn.h" #include "KDbParser_p.h" #include "KDbQuerySchemaParameter.h" #include "KDbRelationship.h" 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('"'); } KDbQuerySchema::KDbQuerySchema() : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new KDbQuerySchemaPrivate(this)) { } KDbQuerySchema::KDbQuerySchema(KDbTableSchema *tableSchema) : KDbFieldList(false)//fields are not owned by KDbQuerySchema object , KDbObject(KDb::QueryObjectType) , d(new KDbQuerySchemaPrivate(this)) { if (tableSchema) { d->masterTable = tableSchema; /*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, KDbConnection *conn) : KDbFieldList(querySchema, false /* !deepCopyFields */) , KDbObject(querySchema) , d(new KDbQuerySchemaPrivate(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->parent()) == &querySchema) { copiedField->setParent(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, conn, const_cast(&querySchema), this); } KDbQuerySchema::~KDbQuerySchema() { delete d; } void KDbQuerySchema::clear() { KDbFieldList::clear(); KDbObject::clear(); d->clear(); } /*virtual*/ bool KDbQuerySchema::insertField(int position, KDbField *field) { return insertFieldInternal(position, field, -1/*don't bind*/, true); } bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field) { return insertFieldInternal(position, field, -1/*don't bind*/, false); } bool KDbQuerySchema::insertField(int position, KDbField *field, int bindToTable) { return insertFieldInternal(position, field, bindToTable, true); } bool KDbQuerySchema::insertInvisibleField(int position, KDbField *field, int bindToTable) { return insertFieldInternal(position, field, bindToTable, false); } bool KDbQuerySchema::insertFieldInternal(int position, KDbField *field, int bindToTable, bool visible) { if (!field) { kdbWarning() << "!field"; return false; } if (position > fieldCount()) { 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 > 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; #ifdef KDB_QUERYSCHEMA_DEBUG querySchemaDebug() << "bound to table" << bindToTable; if (bindToTable == -1) querySchemaDebug() << " "; else querySchemaDebug() << " 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(' ')); querySchemaDebug() << "tablesBoundToColumns == [" << s << "]"; #endif 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; } bool KDbQuerySchema::addField(KDbField* field) { return insertField(fieldCount(), field); } bool KDbQuerySchema::addField(KDbField* field, int bindToTable) { return insertField(fieldCount(), field, bindToTable); } bool KDbQuerySchema::addInvisibleField(KDbField* field) { return insertInvisibleField(fieldCount(), field); } bool KDbQuerySchema::addInvisibleField(KDbField* field, int bindToTable) { return insertInvisibleField(fieldCount(), field, bindToTable); } 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) { //querySchemaDebug() << "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; } bool KDbQuerySchema::addExpressionInternal(const KDbExpression& expr, bool visible) { KDbField *field = new KDbField(this, expr); bool ok; if (visible) { ok = addField(field); } else { ok = addInvisibleField(field); } if (!ok) { delete field; } d->ownedExpressionFields.append(field); return ok; } bool KDbQuerySchema::addExpression(const KDbExpression& expr) { return addExpressionInternal(expr, true); } bool KDbQuerySchema::addInvisibleExpression(const KDbExpression& expr) { return addExpressionInternal(expr, false); } bool KDbQuerySchema::isColumnVisible(int position) const { return (position < fieldCount()) ? d->visibility.testBit(position) : false; } void KDbQuerySchema::setColumnVisible(int position, bool visible) { if (position < fieldCount()) d->visibility.setBit(position, visible); } bool KDbQuerySchema::addAsteriskInternal(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 visible ? addField(asterisk) : addInvisibleField(asterisk); } bool KDbQuerySchema::addAsterisk(KDbQueryAsterisk *asterisk) { return addAsteriskInternal(asterisk, true); } bool KDbQuerySchema::addInvisibleAsterisk(KDbQueryAsterisk *asterisk) { return addAsteriskInternal(asterisk, false); } QDebug operator<<(QDebug dbg, const KDbConnectionAndQuerySchema &connectionAndSchema) { KDbConnection* conn = std::get<0>(connectionAndSchema); const KDbQuerySchema& query = std::get<1>(connectionAndSchema); //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(conn)); 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() << "- 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'; } dbg.nospace() << qPrintable(QString::fromLatin1(" - ORDER BY (%1):\n").arg(query.orderByColumnList()->count())); if (query.orderByColumnList()->isEmpty()) { dbg.nospace() << "\n"; } else { dbg.nospace() << *query.orderByColumnList(); } return dbg.nospace(); } KDbTableSchema* KDbQuerySchema::masterTable() const { if (d->masterTable) return d->masterTable; if (d->tables.isEmpty()) return nullptr; //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 nullptr; } 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) { querySchemaDebug() << (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()) { querySchemaDebug() << "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 = nullptr; 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 nullptr; } bool KDbQuerySchema::contains(KDbTableSchema *table) const { return d->tables.contains(table); } KDbField* KDbQuerySchema::findTableField(const QString &fieldOrTableAndFieldName) const { QString tableName, fieldName; if (!KDb::splitToTableAndFieldParts(fieldOrTableAndFieldName, &tableName, &fieldName, KDb::SetFieldNameIfNoTableName)) { return nullptr; } if (tableName.isEmpty()) { foreach(KDbTableSchema *table, d->tables) { if (table->field(fieldName)) return table->field(fieldName); } return nullptr; } KDbTableSchema *tableSchema = table(tableName); if (!tableSchema) return nullptr; 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); } bool KDbQuerySchema::setColumnAlias(int position, const QString& alias) { if (position >= fieldCount()) { kdbWarning() << "position" << position << "out of range!"; return false; } 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 false; } 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); } bool KDbQuerySchema::hasTableAlias(const QString &name) const { return d->tablePositionForAlias(name) != -1; } int KDbQuerySchema::columnPositionForAlias(const QString& name) const { return d->columnPositionForAlias(name); } bool KDbQuerySchema::hasColumnAlias(const QString &name) const { return d->columnPositionForAlias(name) != -1; } bool KDbQuerySchema::setTableAlias(int position, const QString& alias) { if (position >= d->tables.count()) { kdbWarning() << "position" << position << "out of range!"; return false; } const QString fixedAlias(alias.trimmed()); if (fixedAlias.isEmpty()) { const QString oldAlias(d->tableAliases.take(position)); if (!oldAlias.isEmpty()) { d->removeTablePositionForAlias(oldAlias); } return true; } return 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; } const KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier, ExpandMode mode) const { KDbQueryColumnInfo *ci = columnInfo(conn, identifier, mode); return ci ? ci->field() : nullptr; } KDbField* KDbQuerySchema::field(KDbConnection *conn, const QString& identifier, ExpandMode mode) { return const_cast( static_cast(this)->field(conn, identifier, mode)); } KDbField* KDbQuerySchema::field(int id) { return KDbFieldList::field(id); } const KDbField* KDbQuerySchema::field(int id) const { return KDbFieldList::field(id); } KDbQueryColumnInfo *KDbQuerySchema::columnInfo(KDbConnection *conn, const QString &identifier, ExpandMode mode) const { const KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn); return mode == ExpandMode::Expanded ? cache->columnInfosByNameExpanded.value(identifier) : cache->columnInfosByName.value(identifier); } KDbQueryColumnInfo::Vector KDbQuerySchema::fieldsExpandedInternal( KDbConnection *conn, FieldsExpandedMode mode, bool onlyVisible) const { if (!conn) { kdbWarning() << "Connection required"; return KDbQueryColumnInfo::Vector(); } KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn); const KDbQueryColumnInfo::Vector *realFieldsExpanded = onlyVisible ? &cache->visibleFieldsExpanded : &cache->fieldsExpanded; if (mode == FieldsExpandedMode::WithInternalFields || mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) { //a ref to a proper pointer (as we cache the vector for two cases) KDbQueryColumnInfo::Vector& tmpFieldsExpandedWithInternal = (mode == FieldsExpandedMode::WithInternalFields) ? (onlyVisible ? cache->visibleFieldsExpandedWithInternal : cache->fieldsExpandedWithInternal) : (onlyVisible ? cache->visibleFieldsExpandedWithInternalAndRecordId : cache->fieldsExpandedWithInternalAndRecordId); //special case if (tmpFieldsExpandedWithInternal.isEmpty()) { //glue expanded and internal fields and cache it const int internalFieldCount = cache->internalFields.size(); const int fieldsExpandedVectorSize = realFieldsExpanded->size(); const int size = fieldsExpandedVectorSize + internalFieldCount + ((mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) ? 1 : 0) /*ROWID*/; tmpFieldsExpandedWithInternal.resize(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 = cache->internalFields[i]; tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + i] = info; } } if (mode == FieldsExpandedMode::WithInternalFieldsAndRecordId) { 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; } } return tmpFieldsExpandedWithInternal; } if (mode == FieldsExpandedMode::Default) { return *realFieldsExpanded; } //mode == 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(KDbConnection *conn) const { KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn); return cache->internalFields; } KDbQueryColumnInfo* KDbQuerySchema::expandedOrInternalField(KDbConnection *conn, int index) const { return fieldsExpanded(conn, FieldsExpandedMode::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(); } KDbQuerySchemaFieldsExpanded *KDbQuerySchema::computeFieldsExpanded(KDbConnection *conn) const { KDbQuerySchemaFieldsExpanded *cache = conn->d->fieldsExpanded(this); if (cache) { return cache; } cache = new KDbQuerySchemaFieldsExpanded; QScopedPointer guard(cache); //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; for (KDbField *f : *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)); + ci->d->querySchema = this; + ci->d->connection = conn; list.append(ci); querySchemaDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; cache->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)); + ci->d->querySchema = this; + ci->d->connection = conn; list.append(ci); querySchemaDebug() << "caching (unexpanded) columns order:" << *ci << "at position" << fieldPosition; cache->columnsOrder.insert(ci, fieldPosition); } } } } 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); querySchemaDebug() << "caching (unexpanded) column's order:" << *ci << "at position" << fieldPosition; cache->columnsOrder.insert(ci, fieldPosition); cache->columnsOrderWithoutAsterisks.insert(ci, fieldPosition); //handle lookup field schema KDbLookupFieldSchema *lookupFieldSchema = f->table() ? f->table()->lookupFieldSchema(*f) : nullptr; 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" KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) { KDbTableSchema *lookupTable = conn->tableSchema(recordSource.name()); KDbFieldList* visibleColumns = nullptr; KDbField *boundField = nullptr; if (lookupTable && lookupFieldSchema->boundColumn() < lookupTable->fieldCount() && (visibleColumns = lookupTable->subList(lookupFieldSchema->visibleColumns())) && (boundField = lookupTable->field(lookupFieldSchema->boundColumn()))) { KDbField *visibleColumn = nullptr; // 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*/)); 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() )) { 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() == KDbLookupFieldSchemaRecordSource::Type::Query) { KDbQuerySchema *lookupQuery = conn->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded(conn)); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; KDbQueryColumnInfo *boundColumnInfo = nullptr; 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 = nullptr; // 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*/)); 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() )) { 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 cache->fieldsExpanded.resize(list.count()); cache->visibleFieldsExpanded.resize(list.count()); /*fill (based on prepared 'list' and 'lookup_list'): -the vector -the map -"fields by name" dictionary */ i = -1; int visibleIndex = -1; foreach(KDbQueryColumnInfo* ci, list) { i++; cache->fieldsExpanded[i] = ci; if (ci->isVisible()) { ++visibleIndex; cache->visibleFieldsExpanded[visibleIndex] = ci; } cache->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 (!cache->columnInfosByNameExpanded.contains(ci->alias())) { cache->columnInfosByNameExpanded.insert(ci->alias(), ci); } QString tableAndAlias(ci->alias()); if (ci->field()->table()) tableAndAlias.prepend(ci->field()->table()->name() + QLatin1Char('.')); if (!cache->columnInfosByNameExpanded.contains(tableAndAlias)) { cache->columnInfosByNameExpanded.insert(tableAndAlias, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!cache->columnInfosByName.contains(ci->alias())) { cache->columnInfosByName.insert(ci->alias(), ci); } if (!cache->columnInfosByName.contains(tableAndAlias)) { cache->columnInfosByName.insert(tableAndAlias, ci); } } } else { //no alias: store name and table.name if (!cache->columnInfosByNameExpanded.contains(ci->field()->name())) { cache->columnInfosByNameExpanded.insert(ci->field()->name(), ci); } QString tableAndName(ci->field()->name()); if (ci->field()->table()) tableAndName.prepend(ci->field()->table()->name() + QLatin1Char('.')); if (!cache->columnInfosByNameExpanded.contains(tableAndName)) { cache->columnInfosByNameExpanded.insert(tableAndName, ci); } //the same for "unexpanded" list if (columnInfosOutsideAsterisks.contains(ci)) { if (!cache->columnInfosByName.contains(ci->field()->name())) { cache->columnInfosByName.insert(ci->field()->name(), ci); } if (!cache->columnInfosByName.contains(tableAndName)) { cache->columnInfosByName.insert(tableAndName, ci); } } } } cache->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 cache->internalFields.resize(lookup_list.count()); i = -1; foreach(KDbQueryColumnInfo *ci, lookup_list) { i++; //add it to the internal list cache->internalFields[i] = ci; cache->columnsOrderExpanded.insert(ci, list.count() + i); } //update KDbQueryColumnInfo::indexForVisibleLookupValue() cache for columns numberOfColumnsWithMultipleVisibleFields = 0; for (i = 0; i < cache->fieldsExpanded.size(); i++) { KDbQueryColumnInfo* ci = cache->fieldsExpanded[i]; //! @todo KDbQuerySchema itself will also support lookup fields... KDbLookupFieldSchema *lookupFieldSchema = ci->field()->table() ? ci->field()->table()->lookupFieldSchema(*ci->field()) : nullptr; if (!lookupFieldSchema || lookupFieldSchema->boundColumn() < 0) continue; const KDbLookupFieldSchemaRecordSource recordSource = lookupFieldSchema->recordSource(); if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Table) { KDbTableSchema *lookupTable = conn->tableSchema(recordSource.name()); KDbFieldList* visibleColumns = nullptr; 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(cache->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(cache->fieldsExpanded.size() + index); } } delete visibleColumns; } else if (recordSource.type() == KDbLookupFieldSchemaRecordSource::Type::Query) { KDbQuerySchema *lookupQuery = conn->querySchema(recordSource.name()); if (!lookupQuery) continue; const KDbQueryColumnInfo::Vector lookupQueryFieldsExpanded( lookupQuery->fieldsExpanded(conn)); if (lookupFieldSchema->boundColumn() >= lookupQueryFieldsExpanded.count()) continue; KDbQueryColumnInfo *boundColumnInfo = nullptr; 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(cache->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(cache->fieldsExpanded.size() + index); } } else { kdbWarning() << "unsupported record source type" << recordSource.typeName(); } } if (d->recentConnection != conn) { if (d->recentConnection) { // connection changed: remove old cache d->recentConnection->d->removeFieldsExpanded(this); } d->recentConnection = conn; } conn->d->insertFieldsExpanded(this, guard.take()); return cache; } QHash KDbQuerySchema::columnsOrder(KDbConnection *conn, ColumnsOrderMode mode) const { KDbQuerySchemaFieldsExpanded *cache = computeFieldsExpanded(conn); if (mode == ColumnsOrderMode::UnexpandedList) { return cache->columnsOrder; } else if (mode == ColumnsOrderMode::UnexpandedListWithoutAsterisks) { return cache->columnsOrderWithoutAsterisks; } return cache->columnsOrderExpanded; } QVector KDbQuerySchema::pkeyFieldsOrder(KDbConnection *conn) 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(); querySchemaDebug() << *pkey; d->pkeyFieldsOrder = new QVector(pkey->fieldCount(), -1); d->pkeyFieldCount = 0; const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn)); const int fCount = fieldsExpanded.count(); for (int i = 0; i < fCount; i++) { const KDbQueryColumnInfo *fi = fieldsExpanded[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 */) { querySchemaDebug() << "FIELD" << fi->field()->name() << "IS IN PKEY AT POSITION #" << fieldIndex; (*d->pkeyFieldsOrder)[fieldIndex] = i; d->pkeyFieldCount++; } } querySchemaDebug() << d->pkeyFieldCount << " OUT OF " << pkey->fieldCount() << " PKEY'S FIELDS FOUND IN QUERY " << name(); return *d->pkeyFieldsOrder; } int KDbQuerySchema::pkeyFieldCount(KDbConnection *conn) { (void)pkeyFieldsOrder(conn); /* 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 nullptr; } d->relations.append(r); return r; } KDbQueryColumnInfo::List* KDbQuerySchema::autoIncrementFields(KDbConnection *conn) 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 const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn)); for (int i = 0; i < fieldsExpanded.count(); i++) { KDbQueryColumnInfo *ci = fieldsExpanded[i]; if (ci->field()->table() == mt && ci->field()->isAutoIncrement()) { d->autoincFields->append(ci); } } } 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), conn); //d->lastUsedDriverForAutoIncrementSQLFieldsList = driverWeakPointer; } return d->autoIncrementSqlFieldsList; } static void setResult(const KDbParseInfoInternal &parseInfo, QString *errorMessage, QString *errorDescription) { 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(); KDbQuerySchemaPrivate::setWhereExpressionInternal(this, newWhereExpr); return true; } 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()) ); 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, nullptr, nullptr, nullptr); // 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(KDbConnection *conn) const { QList params; const KDbQueryColumnInfo::Vector fieldsExpanded(this->fieldsExpanded(conn)); for (int i = 0; i < fieldsExpanded.count(); ++i) { KDbQueryColumnInfo *ci = fieldsExpanded[i]; if (!ci->field()->expression().isNull()) { ci->field()->expression().getQueryParameters(¶ms); } } KDbExpression where = whereExpression(); if (!where.isNull()) { where.getQueryParameters(¶ms); } return params; } 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; } diff --git a/src/KDbQuerySchema_p.h b/src/KDbQuerySchema_p.h index 30a5430d..90e15d71 100644 --- a/src/KDbQuerySchema_p.h +++ b/src/KDbQuerySchema_p.h @@ -1,252 +1,280 @@ /* 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 "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) public: explicit KDbQuerySchemaPrivate(KDbQuerySchema* q, KDbQuerySchemaPrivate* copy = nullptr); ~KDbQuerySchemaPrivate(); //! @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(); bool setColumnAlias(int position, const QString& alias); inline bool setTableAlias(int position, const QString& alias) { if (tablePositionsForAliases.contains(alias.toLower())) { return false; } tableAliases.insert(position, alias.toLower()); tablePositionsForAliases.insert(alias.toLower(), position); return true; } 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. Can be @c nullptr. 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 protected: void tryRegenerateExprAliases(); bool 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; /*! 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; /*! 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; /*! Set by insertField(): true, if aliases for expression columns should be generated on next columnAlias() call. */ bool regenerateExprAliases; //! Points to connection recently used for caching //! @todo use equivalent of QPointer KDbConnection *recentConnection = nullptr; //! Owned fields created by KDbQuerySchema::addExpressionInternal() KDbField::List ownedExpressionFields; }; //! Information about expanded fields for a single query schema, used for caching class KDbQuerySchemaFieldsExpanded { public: inline KDbQuerySchemaFieldsExpanded() { } inline ~KDbQuerySchemaFieldsExpanded() { qDeleteAll(fieldsExpanded); qDeleteAll(internalFields); } /*! 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 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; QHash columnInfosByNameExpanded; QHash columnInfosByName; //!< Same as columnInfosByNameExpanded but asterisks are skipped //! Fields created for multiple joined columns like a||' '||b||' '||c KDbField::List ownedVisibleFields; }; /** * Return identifier string @a name escaped using @a conn connection and type @a escapingType * * @a conn is only used for KDb::DriverEscaping type. If @a conn is missing for this type, * identifier is escaped using double quotes ("). */ QString escapeIdentifier(const QString& name, KDbConnection *conn, KDb::IdentifierEscapingType escapingType); #endif