[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [kdb] /: Fix handling ORDER BY part of queries
From:       Jaroslaw Staniek <null () kde ! org>
Date:       2018-05-16 8:16:28
Message-ID: E1fIrbo-00036c-C8 () code ! kde ! org
[Download RAW message or body]

Git commit 3414fbddffa433b23d43376b44fef5421b734250 by Jaroslaw Staniek.
Committed on 16/05/2018 at 08:15.
Pushed by staniek into branch 'master'.

Fix handling ORDER BY part of queries

Summary:
- new APIs
- maintain relations between connections, queries and column infos
- fix cloning queries with ORDER BY
- update and add autotests

FIXED-IN:3.2
BUG:392753
CCBUG:393705

Test Plan:
Required: KEXI & KDb 3.2 (current master)

Test 1: Open report and query from https://bugs.kde.org/show_bug.cgi?id=392753#c0 in \
                design and data views.
Expected: works, no crashes

Test 2: Try these on a standard Persons and Cars test:
select id from cars order by owner;
select id from cars order by owner, model, id;
select id, model from cars order by 2, 1;

Expected: works, no crash

Reviewers: piggz

Reviewed By: piggz

Subscribers: Kexi-Devel-list

Tags: #kdb

Differential Revision: https://phabricator.kde.org/D12873

M  +1    -1    CMakeLists.txt
M  +3    -0    autotests/CMakeLists.txt
A  +212  -0    autotests/OrderByColumnTest.cpp     [License: LGPL (v2+)]
A  +46   -0    autotests/OrderByColumnTest.h     [License: LGPL (v2+)]
M  +2    -2    src/KDbNativeStatementBuilder.cpp
M  +87   -28   src/KDbOrderByColumn.cpp
M  +40   -13   src/KDbOrderByColumn.h
M  +17   -28   src/KDbQueryColumnInfo.cpp
M  +25   -1    src/KDbQueryColumnInfo.h
M  +18   -4    src/KDbQuerySchema.cpp
M  +29   -1    src/KDbQuerySchema_p.h

https://commits.kde.org/kdb/3414fbddffa433b23d43376b44fef5421b734250

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c5f0c4b0..157377b0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ find_package(ECM 1.8.0 REQUIRED NO_MODULE)
 set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH} \
${ECM_KDE_MODULE_DIR})  include(SetKDbCMakePolicies NO_POLICY_SCOPE)
 
-project(KDb VERSION 3.1.90) # Update this
+project(KDb VERSION 3.1.91) # Update this
 
 include(KDbAddTests)
 include(KDbAddExamples)
diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index ee4d65a6..bd344989 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -43,6 +43,7 @@ ecm_add_tests(
     DriverTest.cpp
     ExpressionsTest.cpp
     MissingTableTest.cpp
+    OrderByColumnTest.cpp
     QuerySchemaTest.cpp
     KDbTest.cpp
 
@@ -52,6 +53,8 @@ ecm_add_tests(
 
 target_compile_definitions(MissingTableTest PRIVATE \
-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )  
+target_compile_definitions(OrderByColumnTest PRIVATE KDB_DEPRECATED=)
+
 if(NOT WIN32) #TODO enable for Windows when headers_test.sh is ported e.g. to python
     add_subdirectory(headers)
 endif()
diff --git a/autotests/OrderByColumnTest.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 <staniek@kde.org>
+
+   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 <KDbExpression>
+#include <KDbOrderByColumn>
+#include <KDbQueryAsterisk>
+#include <KDbQuerySchema>
+#include <KDbNativeStatementBuilder>
+
+#include <QTest>
+
+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 <staniek@kde.org>
+
+   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
@@ -389,7 +389,7 @@ static bool selectStatementInternal(KDbEscapedString *target,
 
     // ORDER BY
     KDbEscapedString orderByString(querySchema->orderByColumnList()->toSqlString(
-        !singleTable /*includeTableName*/, connection, dialect));
+        !singleTable /*includeTableName*/, connection, querySchema, dialect));
     const QVector<int> pkeyFieldsOrder(querySchema->pkeyFieldsOrder(connection));
     if (dialect == KDb::DriverEscaping  && orderByString.isEmpty() && \
                !pkeyFieldsOrder.isEmpty()) {
         // Native only: add automatic ORDER BY if there is no explicitly defined one
@@ -408,7 +408,7 @@ static bool selectStatementInternal(KDbEscapedString *target,
             automaticPKOrderBy.appendColumn(ci);
         }
         orderByString = automaticPKOrderBy.toSqlString(!singleTable \
                /*includeTableName*/,
-                                                       connection, dialect);
+                                                       connection, querySchema, \
dialect);  }
     if (!orderByString.isEmpty())
         sql += (" ORDER BY " + orderByString);
diff --git a/src/KDbOrderByColumn.cpp b/src/KDbOrderByColumn.cpp
index 6986cd95..4155db7a 100644
--- a/src/KDbOrderByColumn.cpp
+++ b/src/KDbOrderByColumn.cpp
@@ -1,5 +1,5 @@
 /* This file is part of the KDE project
-   Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
+   Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
@@ -27,7 +27,7 @@ class Q_DECL_HIDDEN KDbOrderByColumn::Private
 {
 public:
     Private()
-        : column(nullptr)
+        : columnIndex(-1)
         , pos(-1)
         , field(nullptr)
         , order(KDbOrderByColumn::SortOrder::Ascending)
@@ -36,9 +36,24 @@ public:
     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);
@@ -47,8 +62,18 @@ public:
         return KDbOrderByColumnPrivateArgs((*this)) == \
KDbOrderByColumnPrivateArgs(other);  }
 
-    //! Column to sort, @c nullptr if field is non-0.
-    KDbQueryColumnInfo* column;
+    //! Query schema that owns the KDbQueryColumnInfo and thus also this \
KDbOrderByColumn object. +    //! Cached for performance, can be cached since \
lifetime of the KDbOrderByColumn object depends +    //! on the query. @c nullptr if \
columnIndex is not provided. @since 3.2 +    const KDbQuerySchema *querySchema = \
nullptr; +
+    //! Connection used to compute expanded fields. Like querySchema, connection is \
cached for +    //! performance and can be cached since lifetime of the \
KDbOrderByColumn object depends on the +    //! connection. @c nullptr if columnIndex \
is not provided. @since 3.2 +    KDbConnection *connection = nullptr;
+
+    //! Index of column to sort, -1 if field is present. @since 3.2
+    int columnIndex;
 
     //! Value that indicates that column to sort (columnIndex) has been specified by \
                providing its
     //! position, not name. For example, using "SELECT a, b FROM T ORDER BY 2".
@@ -96,22 +121,17 @@ KDbOrderByColumn *KDbOrderByColumn::copy(KDbConnection *conn, \
KDbQuerySchema *fr  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);
     }
@@ -120,7 +140,10 @@ KDbOrderByColumn *KDbOrderByColumn::copy(KDbConnection *conn, \
KDbQuerySchema *fr  
 KDbQueryColumnInfo* KDbOrderByColumn::column() const
 {
-    return d->column;
+    if (d->columnIndex < 0 || !d->querySchema || !d->connection) {
+        return nullptr;
+    }
+    return d->querySchema->expandedOrInternalField(d->connection, d->columnIndex);
 }
 
 int KDbOrderByColumn::position() const
@@ -179,38 +202,67 @@ QDebug operator<<(QDebug dbg, const KDbOrderByColumn& order)
 
 KDbEscapedString KDbOrderByColumn::toSqlString(bool includeTableName,
                                                KDbConnection *conn,
+                                               KDbQuerySchema *query,
                                                KDb::IdentifierEscapingType \
escapingType) const  {
     const QByteArray orderString(d->order == KDbOrderByColumn::SortOrder::Ascending \
? "" : " DESC");  KDbEscapedString fieldName, tableName, collationString;
-    if (d->column) {
+    KDbQueryColumnInfo *col = column();
+    if (col) {
         if (d->pos > -1)
             return KDbEscapedString::number(d->pos + 1) + orderString;
         else {
-            if (includeTableName && d->column->alias().isEmpty()) {
-                tableName = \
KDbEscapedString(escapeIdentifier(d->column->field()->table()->name(), conn, \
escapingType)); +            if (includeTableName && col->field()->table() && \
col->alias().isEmpty()) { +                tableName = \
KDbEscapedString(escapeIdentifier(col->field()->table()->name(), conn, \
escapingType));  tableName += '.';
             }
-            fieldName = KDbEscapedString(escapeIdentifier(d->column->aliasOrName(), \
conn, escapingType)); +            fieldName = \
KDbEscapedString(escapeIdentifier(col->aliasOrName(), conn, escapingType));  }
-        if (d->column->field()->isTextType() && escapingType == KDb::DriverEscaping) \
{ +        if (conn && col->field()->isTextType() && escapingType == \
KDb::DriverEscaping) {  collationString = conn->driver()->collationSql();
         }
     }
     else {
-        if (d->field && includeTableName) {
+        QString aliasOrName;
+        if (includeTableName && d->field && d->field->table()) {
             tableName = KDbEscapedString(escapeIdentifier(d->field->table()->name(), \
conn, escapingType));  tableName += '.';
+        } else if (d->field && conn && query) {
+            if (d->field->isExpression()) {
+                const int indexOfField = query->indexOf(*d->field);
+                aliasOrName = query->columnAlias(indexOfField);
+                if (aliasOrName.isEmpty()) {
+                    kdbWarning() << "This field does not belong to specified query:" \
<< *d->field +                                 << endl << "cannot find alias";
+                    aliasOrName = QLatin1String("?unknown_field?");
+                }
+            } else {
+                KDbQueryColumnInfo *ci = query->columnInfo(conn, d->field->name());
+                if (ci) {
+                    aliasOrName = ci->aliasOrName();
+                }
+            }
+        }
+        if (aliasOrName.isEmpty()) {
+            // The field is not present on the SELECT list but is still correct,
+            // e.g. SELECT id FROM cars ORDER BY owner
+            aliasOrName = d->field ? d->field->name() : \
QLatin1String("?missing_field?")/*error*/;  }
-        fieldName = KDbEscapedString(escapeIdentifier(
-            d->field ? d->field->name() : QLatin1String("??")/*error*/, conn, \
                escapingType));
-        if (d->field && d->field->isTextType() && escapingType == \
KDb::DriverEscaping) { +        fieldName = \
KDbEscapedString(escapeIdentifier(aliasOrName, conn, escapingType)); +        if \
(conn && d->field && d->field->isTextType() && escapingType == KDb::DriverEscaping) { \
collationString = conn->driver()->collationSql();  }
     }
     return tableName + fieldName + collationString + orderString;
 }
 
+KDbEscapedString KDbOrderByColumn::toSqlString(bool includeTableName,
+                                               KDbConnection *conn,
+                                               KDb::IdentifierEscapingType \
escapingType) const +{
+    return toSqlString(includeTableName, conn, nullptr, escapingType);
+}
+
 //=======================================
 
 class Q_DECL_HIDDEN KDbOrderByColumnList::Private
@@ -395,17 +447,24 @@ QDebug operator<<(QDebug dbg, const KDbOrderByColumnList& list)
 }
 
 KDbEscapedString KDbOrderByColumnList::toSqlString(bool includeTableNames, \
                KDbConnection *conn,
-                                       KDb::IdentifierEscapingType escapingType) \
const +                                                   KDbQuerySchema *query,
+                                                   KDb::IdentifierEscapingType \
escapingType) const  {
     KDbEscapedString string;
     for (QList<KDbOrderByColumn*>::ConstIterator it(constBegin()); it != constEnd(); \
++it) {  if (!string.isEmpty())
             string += ", ";
-        string += (*it)->toSqlString(includeTableNames, conn, escapingType);
+        string += (*it)->toSqlString(includeTableNames, conn, query, escapingType);
     }
     return string;
 }
 
+KDbEscapedString KDbOrderByColumnList::toSqlString(bool includeTableNames, \
KDbConnection *conn, +                                                   \
KDb::IdentifierEscapingType escapingType) const +{
+    return toSqlString(includeTableNames, conn, nullptr, escapingType);
+}
+
 void KDbOrderByColumnList::clear()
 {
     qDeleteAll(d->data);
diff --git a/src/KDbOrderByColumn.h b/src/KDbOrderByColumn.h
index 47408e0c..96c6cdb6 100644
--- a/src/KDbOrderByColumn.h
+++ b/src/KDbOrderByColumn.h
@@ -1,5 +1,5 @@
 /* This file is part of the KDE project
-   Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
+   Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
@@ -89,13 +89,26 @@ public:
     //! @since 3.1
     inline bool operator!=(const KDbOrderByColumn &other) const { return \
!operator==(other); }  
-    /*! @return a string like "name ASC" usable for building an SQL statement.
-     If @a includeTableNames is true (the default) field is output in a form
-     of "tablename.fieldname" (but only if fieldname is not a name of alias).
+    /** Return an SQL string like "name ASC" or "2 DESC" usable for building an SQL \
statement +     *
+     * If @a includeTableNames is @c true fields that are related to a table are
+     * printed as "tablename.fieldname".
+     *
+     * @a escapingType can be used to alter default escaping type.
+     * If @a conn is not provided for DriverEscaping, no escaping is performed.
+     * If @a query is provided, it can be used to obtain alias information.
+     *
+     * @since 3.2
+     */
+    KDbEscapedString toSqlString(bool includeTableName,
+                                 KDbConnection *conn, KDbQuerySchema *query,
+                                 KDb::IdentifierEscapingType escapingType = \
KDb::DriverEscaping) const;  
-     @a escapingType can be used to alter default escaping type.
-     If @a conn is not provided for DriverEscaping, no escaping is performed. */
-    KDbEscapedString toSqlString(bool includeTableName = true,
+    /*! @overload
+
+     @deprecated since 3.2, use overload that also takes query schema
+    */
+    KDB_DEPRECATED KDbEscapedString toSqlString(bool includeTableName = true,
                                  KDbConnection *conn = nullptr,
                                  KDb::IdentifierEscapingType escapingType = \
KDb::DriverEscaping) const;  
@@ -201,15 +214,29 @@ public:
      */
     QList<KDbOrderByColumn*>::ConstIterator constEnd() const;
 
-    /*! @return an SQL string like "name ASC, 2 DESC" usable for building an SQL \
                statement.
-     If @a includeTableNames is true (the default) fields are output in a form
-     of "tablename.fieldname".
+    /** Return an SQL string like "name ASC, 2 DESC" usable for building an SQL \
statement +     *
+     * If @a includeTableNames is @c true (the default) fields that are related to a \
table are +     * printed as "tablename.fieldname".
+     *
+     * @a escapingType can be used to alter default escaping type.
+     * If @a conn is not provided for DriverEscaping, no escaping is performed.
+     * If @a query is provided, it can be used to obtain alias information.
+     *
+     * @since 3.2
+     */
+    KDbEscapedString toSqlString(bool includeTableNames,
+                                 KDbConnection *conn, KDbQuerySchema *query,
+                                 KDb::IdentifierEscapingType escapingType = \
KDb::DriverEscaping) const; +
+    /*! @overload
 
-     @a escapingType can be used to alter default escaping type.
-     If @a conn is not provided for DriverEscaping, no escaping is performed. */
-    KDbEscapedString toSqlString(bool includeTableNames = true,
+     @deprecated since 3.2, use overload that also takes query schema
+    */
+    KDB_DEPRECATED KDbEscapedString toSqlString(bool includeTableNames = true,
                                  KDbConnection *conn = nullptr,
                                  KDb::IdentifierEscapingType escapingType = \
KDb::DriverEscaping) const; +
 private:
     class Private;
     Private * const d;
diff --git a/src/KDbQueryColumnInfo.cpp b/src/KDbQueryColumnInfo.cpp
index 123ea45b..e00afa4f 100644
--- a/src/KDbQueryColumnInfo.cpp
+++ b/src/KDbQueryColumnInfo.cpp
@@ -1,5 +1,5 @@
 /* This file is part of the KDE project
-   Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
+   Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
@@ -18,38 +18,12 @@
 */
 
 #include "KDbQueryColumnInfo.h"
+#include "KDbQuerySchema_p.h"
 #include "KDbTableSchema.h"
 #include "KDbField.h"
 #include "KDbField_p.h"
 #include "kdb_debug.h"
 
-//! @internal
-class Q_DECL_HIDDEN KDbQueryColumnInfo::Private
-{
-public:
-    Private(KDbField *f, const QString& a, bool v, KDbQueryColumnInfo *foreign)
-        : field(f)
-        , alias(a)
-        , visible(v)
-        , indexForVisibleLookupValue(-1)
-        , foreignColumn(foreign)
-    {
-    }
-
-    KDbField *field;
-    QString alias;
-
-    //! @c true if this column is visible to the user (and its data is fetched by \
                the engine)
-    bool visible;
-
-    /*! Index of column with visible lookup value within the 'fields expanded' \
                vector.
-     @see KDbQueryColumnInfo::indexForVisibleLookupValue() */
-    int indexForVisibleLookupValue;
-
-    //! Non-nullptr if this column is a visible column for @a foreignColumn
-    KDbQueryColumnInfo *foreignColumn;
-};
-
 KDbQueryColumnInfo::KDbQueryColumnInfo(KDbField *f, const QString& alias, bool \
visible,  KDbQueryColumnInfo *foreignColumn)
         : d(new Private(f, alias, visible, foreignColumn))
@@ -126,6 +100,21 @@ 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;
diff --git a/src/KDbQueryColumnInfo.h b/src/KDbQueryColumnInfo.h
index c84b71c3..3a0fcfd1 100644
--- a/src/KDbQueryColumnInfo.h
+++ b/src/KDbQueryColumnInfo.h
@@ -1,5 +1,5 @@
 /* This file is part of the KDE project
-   Copyright (C) 2003-2016 Jarosław Staniek <staniek@kde.org>
+   Copyright (C) 2003-2018 Jarosław Staniek <staniek@kde.org>
 
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
@@ -26,7 +26,9 @@
 #include <QVector>
 #include <QString>
 
+class KDbConnection;
 class KDbField;
+class KDbQuerySchema;
 
 //! @short Helper class that assigns additional information for the column in a \
query  /*! The following information is assigned:
@@ -89,7 +91,29 @@ public:
     //! @overload KDbQueryColumnInfo *foreignColumn();
     const KDbQueryColumnInfo *foreignColumn() const;
 
+    /**
+     * Returns query schema for this column
+     *
+     * @since 3.2
+     */
+    const KDbQuerySchema* querySchema() const;
+
+    /**
+     * Returns connection for this column
+     *
+     * @since 3.2
+     */
+    KDbConnection* connection();
+
+    /**
+     * @overload
+     *
+     * @since 3.2
+     */
+    const KDbConnection* connection() const;
+
 private:
+    friend class KDbQuerySchema;
     class Private;
     Private * const d;
     Q_DISABLE_COPY(KDbQueryColumnInfo)
diff --git a/src/KDbQuerySchema.cpp b/src/KDbQuerySchema.cpp
index 2a262cb7..e03b3652 100644
--- a/src/KDbQuerySchema.cpp
+++ b/src/KDbQuerySchema.cpp
@@ -749,6 +749,8 @@ KDbQueryColumnInfo::Vector \
KDbQuerySchema::fieldsExpandedInternal(  if (!d->fakeRecordIdField) {
                     d->fakeRecordIdField = new KDbField(QLatin1String("rowID"), \
                KDbField::BigInteger);
                     d->fakeRecordIdCol = new \
KDbQueryColumnInfo(d->fakeRecordIdField, QString(), true); +                    \
d->fakeRecordIdCol->d->querySchema = this; +                    \
d->fakeRecordIdCol->d->connection = conn;  }
                 tmpFieldsExpandedWithInternal[fieldsExpandedVectorSize + \
internalFieldCount] = d->fakeRecordIdCol;  }
@@ -821,6 +823,8 @@ KDbQuerySchemaFieldsExpanded \
*KDbQuerySchema::computeFieldsExpanded(KDbConnectio  foreach(KDbField *ast_f, \
                *ast_fields) {
                     KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(ast_f, \
QString()/*no field for asterisk!*/,  isColumnVisible(fieldPosition));
+                    ci->d->querySchema = this;
+                    ci->d->connection = conn;
                     list.append(ci);
                     kdbDebug() << "caching (unexpanded) columns order:" << *ci << \
"at position" << fieldPosition;  cache->columnsOrder.insert(ci, fieldPosition);
@@ -835,6 +839,8 @@ KDbQuerySchemaFieldsExpanded \
*KDbQuerySchema::computeFieldsExpanded(KDbConnectio  //      list.append(tab_f);
                         KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(tab_f, \
QString()/*no field for asterisk!*/,  isColumnVisible(fieldPosition));
+                        ci->d->querySchema = this;
+                        ci->d->connection = conn;
                         list.append(ci);
                         kdbDebug() << "caching (unexpanded) columns order:" << *ci \
<< "at position" << fieldPosition;  cache->columnsOrder.insert(ci, fieldPosition);
@@ -844,6 +850,8 @@ KDbQuerySchemaFieldsExpanded \
*KDbQuerySchema::computeFieldsExpanded(KDbConnectio  } else {
             //a single field
             KDbQueryColumnInfo *ci = new KDbQueryColumnInfo(f, \
columnAlias(fieldPosition), isColumnVisible(fieldPosition)); +            \
ci->d->querySchema = this; +            ci->d->connection = conn;
             list.append(ci);
             columnInfosOutsideAsterisks.insert(ci, true);
             kdbDebug() << "caching (unexpanded) column's order:" << *ci << "at \
position" << fieldPosition; @@ -883,8 +891,11 @@ KDbQuerySchemaFieldsExpanded \
                *KDbQuerySchema::computeFieldsExpanded(KDbConnectio
                         cache->ownedVisibleFields.append(visibleColumn);   // \
remember to delete later  }
 
-                    lookup_list.append(
-                        new KDbQueryColumnInfo(visibleColumn, QString(), \
true/*visible*/, ci/*foreign*/)); +                    KDbQueryColumnInfo *lookupCi = \
new KDbQueryColumnInfo( +                        visibleColumn, QString(), true \
/*visible*/, ci /*foreign*/); +                    lookupCi->d->querySchema = this;
+                    lookupCi->d->connection = conn;
+                    lookup_list.append(lookupCi);
                     /*
                               //add visibleField to the list of SELECTed fields if \
                it is not yes present there
                               if (!findTableField( \
visibleField->table()->name()+"."+visibleField->name() )) { @@ -938,8 +949,11 @@ \
                KDbQuerySchemaFieldsExpanded \
                *KDbQuerySchema::computeFieldsExpanded(KDbConnectio
                     cache->ownedVisibleFields.append(visibleColumn);   // remember \
to delete later  }
 
-                lookup_list.append(
-                    new KDbQueryColumnInfo(visibleColumn, QString(), \
true/*visible*/, ci/*foreign*/)); +                KDbQueryColumnInfo *lookupCi = new \
KDbQueryColumnInfo( +                    visibleColumn, QString(), true /*visible*/, \
ci /*foreign*/); +                lookupCi->d->querySchema = this;
+                lookupCi->d->connection = conn;
+                lookup_list.append(lookupCi);
                 /*
                         //add visibleField to the list of SELECTed fields if it is \
                not yes present there
                         if (!findTableField( \
                visibleField->table()->name()+"."+visibleField->name() )) {
diff --git a/src/KDbQuerySchema_p.h b/src/KDbQuerySchema_p.h
index 30a5430d..90e15d71 100644
--- a/src/KDbQuerySchema_p.h
+++ b/src/KDbQuerySchema_p.h
@@ -22,6 +22,7 @@
 
 #include "KDbDriver.h"
 #include "KDbExpression.h"
+#include "KDbQueryColumnInfo.h"
 #include "KDbQuerySchema.h"
 
 #include <QBitArray>
@@ -29,7 +30,34 @@
 
 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)


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic