From 6006c91bc2593c611d15e83ee605c1e289a4b3a4 Mon Sep 17 00:00:00 2001
From: Bruce Toll <btoll@dhsus.com>
Date: Mon, 20 Apr 2015 06:11:18 -0400
Subject: [PATCH] Support specialized orderBy clause in QueryModel.

Add createOrderBy method to Dbo::QueryModel to support customization of
the SQL ORDER BY clause.

See dbo_test25 in DboTest.C for usage example and test code.
---
 src/Wt/Dbo/QueryModel        |  16 +++++
 src/Wt/Dbo/QueryModel_impl.h |  10 +++-
 test/dbo/DboTest.C           | 136 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 160 insertions(+), 2 deletions(-)

diff --git a/src/Wt/Dbo/QueryModel b/src/Wt/Dbo/QueryModel
index aef8e1a..ae6bc6d 100644
--- a/src/Wt/Dbo/QueryModel
+++ b/src/Wt/Dbo/QueryModel
@@ -286,9 +286,25 @@ public:
    *
    * This sorts the model by changing the query using
    * Query<BindStrategy>::orderBy().
+   *
+   * \sa createOrderBy()
    */
   virtual void sort(int column, SortOrder order = AscendingOrder);
 
+  /*! \brief Create specialized orderBy clause for sort
+   *
+   * The sort() method calls createOrderBy() to format a SQL
+   * clause for use in Query<BindStrategy>::orderBy().
+   * The default is to return a string that will sort on
+   * a single column in the specified order.
+   *
+   * Customize this method if you need a more complex orderBy
+   * clause, e.g. to break ties consistently.
+   *
+   * \sa sort()
+   */
+  virtual std::string createOrderBy(int column, SortOrder order);
+
   /*! \brief Inserts one or more rows.
    *
    * Row insertions are only supported at the end (\p row ==
diff --git a/src/Wt/Dbo/QueryModel_impl.h b/src/Wt/Dbo/QueryModel_impl.h
index 83b8ed5..7bc288b 100644
--- a/src/Wt/Dbo/QueryModel_impl.h
+++ b/src/Wt/Dbo/QueryModel_impl.h
@@ -206,14 +206,20 @@ void QueryModel<Result>::sort(int column, SortOrder order)
 
   invalidateData();
 
-  query_.orderBy(fields_[columns_[column].fieldIdx_].sql() + " "
-		 + (order == AscendingOrder ? "asc" : "desc"));
+  query_.orderBy(createOrderBy(column, order));
 
   cachedRowCount_ = rc;
   dataReloaded();
 }
 
 template <class Result>
+std::string QueryModel<Result>::createOrderBy(int column, SortOrder order)
+{
+  return fieldInfo(column).sql() + " "
+		 + (order == AscendingOrder ? "asc" : "desc");
+}
+
+template <class Result>
 Result QueryModel<Result>::stableResultRow(int row) const
 {
   StableResultIdMap::const_iterator i = stableIds_.find(row);
diff --git a/test/dbo/DboTest.C b/test/dbo/DboTest.C
index 6d86342..ccea178 100644
--- a/test/dbo/DboTest.C
+++ b/test/dbo/DboTest.C
@@ -31,6 +31,7 @@ class B;
 class C;
 class D;
 class E;
+class F;
 
 bool fractionalSeconds = true;
 
@@ -85,6 +86,7 @@ struct DboFixture : DboFixtureBase
     session_->mapClass<C>(SCHEMA "table_c");
     session_->mapClass<D>(SCHEMA "table_d");
     session_->mapClass<E>(SCHEMA "table_e");
+    session_->mapClass<F>(SCHEMA "table_f");
 
     try {
       session_->dropTables();
@@ -445,6 +447,27 @@ public:
   }
 };
 
+class F {
+public:
+  std::string firstName;
+  std::string lastName;
+  std::string gender;
+
+  F() { }
+
+  F(const std::string& aFirstName, const std::string& aLastName, const std::string& aGender)
+    : firstName(aFirstName), lastName(aLastName), gender(aGender)
+  { }
+
+  template <class Action>
+  void persist(Action& a)
+  {
+    dbo::field(a, firstName, "first_name", 1000);
+    dbo::field(a, lastName,  "last_name",  1000);
+    dbo::field(a, gender,  "gender",  1000);
+  }
+};
+
 BOOST_AUTO_TEST_CASE( dbo_test1 )
 {
   DboFixture f;
@@ -2178,3 +2201,116 @@ BOOST_AUTO_TEST_CASE( dbo_test24c )
     BOOST_REQUIRE(dc.get<1>()->name == "c1");
   }
 }
+
+BOOST_AUTO_TEST_CASE( dbo_test25 )
+{
+  DboFixture f;
+  dbo::Session *session_ = f.session_;
+
+  {
+    dbo::Transaction t(*session_);
+
+    dbo::ptr<F> e1 = session_->add(new F("Alice",  "Kramden", "Female"));
+    dbo::ptr<F> e2 = session_->add(new F("Ralph",  "Kramden", "Male"));
+    dbo::ptr<F> e3 = session_->add(new F("Trixie", "Norton", "Female"));
+    dbo::ptr<F> e4 = session_->add(new F("Ed",     "Norton", "Male"));
+
+    t.commit();
+  }
+
+  {
+    dbo::Query< dbo::ptr<F> > query = session_->find<F>().orderBy("id");
+    dbo::QueryModel< dbo::ptr<F> > *model
+      = new dbo::QueryModel< dbo::ptr<F> >();
+
+    model->setQuery(query);
+    model->addAllFieldsAsColumns();
+
+    BOOST_REQUIRE(model->columnCount() == 5);
+    BOOST_REQUIRE(model->rowCount() == 4);
+    BOOST_REQUIRE(Wt::asString(model->headerData(0)) == "id");
+    BOOST_REQUIRE(Wt::asString(model->headerData(1)) == "version");
+    BOOST_REQUIRE(Wt::asString(model->headerData(2)) == "first_name");
+    BOOST_REQUIRE(Wt::asString(model->headerData(3)) == "last_name");
+    BOOST_REQUIRE(Wt::asString(model->headerData(4)) == "gender");
+
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Trixie");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Ed");
+
+    // sort on first name
+    model->sort(2, Wt::SortOrder::AscendingOrder);
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Ed");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Trixie");
+
+    delete model;
+  }
+
+  {
+    class custom_sort_model : public dbo::QueryModel< dbo::ptr<F> > {
+    public:
+      custom_sort_model() : dbo::QueryModel< dbo::ptr<F> >() { }
+
+      virtual std::string createOrderBy(int column, Wt::SortOrder order)
+      {
+        std::string dir = (order == Wt::SortOrder::AscendingOrder ? "asc" : "desc");
+        return fieldInfo(column).name() + " " + dir +
+          ((column != 3) ? ", last_name" : "") +
+          ((column != 2) ? ", first_name" : "");
+      }
+    };
+
+    dbo::Query< dbo::ptr<F> > query = session_->find<F>().orderBy("id");
+
+    custom_sort_model *model = new custom_sort_model();
+
+    model->setQuery(query);
+    model->addAllFieldsAsColumns();
+
+    BOOST_REQUIRE(model->columnCount() == 5);
+    BOOST_REQUIRE(model->rowCount() == 4);
+    BOOST_REQUIRE(Wt::asString(model->headerData(0)) == "id");
+    BOOST_REQUIRE(Wt::asString(model->headerData(1)) == "version");
+    BOOST_REQUIRE(Wt::asString(model->headerData(2)) == "first_name");
+    BOOST_REQUIRE(Wt::asString(model->headerData(3)) == "last_name");
+    BOOST_REQUIRE(Wt::asString(model->headerData(4)) == "gender");
+
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Trixie");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Ed");
+
+    // sort on last name ascending, then first name ascending
+    model->sort(3, Wt::SortOrder::AscendingOrder);
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Ed");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Trixie");
+
+    // sort on last name descending, then first name ascending
+    model->sort(3, Wt::SortOrder::DescendingOrder);
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Ed");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Trixie");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Ralph");
+
+    // sort on first name, then last name ascending
+    model->sort(2, Wt::SortOrder::AscendingOrder);
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Ed");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Trixie");
+
+    // sort on gender, then last name, then first name
+    model->sort(4, Wt::SortOrder::AscendingOrder);
+    BOOST_REQUIRE(Wt::asString(model->data(0, 2)) == "Alice");
+    BOOST_REQUIRE(Wt::asString(model->data(1, 2)) == "Trixie");
+    BOOST_REQUIRE(Wt::asString(model->data(2, 2)) == "Ralph");
+    BOOST_REQUIRE(Wt::asString(model->data(3, 2)) == "Ed");
+
+    delete model;
+  }
+}
-- 
1.9.1

