From 47baca8a9b23e7f6537db113c639a61e3b89a9e2 Mon Sep 17 00:00:00 2001
From: Bruce Toll <btoll@dhsus.com>
Date: Sat, 2 Aug 2014 16:02:13 -0400
Subject: [PATCH] Fix for dbo ptr_tuples without version fields.

Remove assumption that dbo ptr_tuples contain ptr objects with
versionFields. This fixes an off-by-one in the mapping of fields for
subsequent dbo ptr objects in the tuple.

NOTE: this issue only affected dbo ptr objects with surrogate ids.

Adds three dbo tests:

dbo_test23: Drives problem/verifies fix for surrogate ids.
            This is for the case where the dbo ptr object is not null.

dbo_test24: Drives problem/verifies fix for surrogate ids.
            This is for the case where dbo ptr object is null.
            This test uses a right join and is currently only available
            for Postgres.

dbo_test25: A similar test for natural ids, which are not affected by
            this issue.
---
 src/Wt/Dbo/Session_impl.h |   4 +-
 test/dbo/DboTest.C        | 101 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 103 insertions(+), 2 deletions(-)

diff --git a/src/Wt/Dbo/Session_impl.h b/src/Wt/Dbo/Session_impl.h
index b76350a..2c85d33 100644
--- a/src/Wt/Dbo/Session_impl.h
+++ b/src/Wt/Dbo/Session_impl.h
@@ -156,7 +156,7 @@ ptr<C> Session::loadWithLongLongId(SqlStatement *statement, int& column)
      * dbo_test4c.
      */
     if (!statement->getResult(column++, &id)) {
-      column += (int)mapping->fields.size() + 1; // + version
+      column += (int)mapping->fields.size() + !!mapping->versionFieldName;
       return ptr<C>();
     }
 
@@ -174,7 +174,7 @@ ptr<C> Session::loadWithLongLongId(SqlStatement *statement, int& column)
       if (!i->second->isLoaded())
 	implLoad<C>(*i->second, statement, column);
       else
-	column += (int)mapping->fields.size() + 1; // + version
+	column += (int)mapping->fields.size() + !!mapping->versionFieldName;
 
       return ptr<C>(i->second);
     }
diff --git a/test/dbo/DboTest.C b/test/dbo/DboTest.C
index 1f9c9e5..30f0fc6 100644
--- a/test/dbo/DboTest.C
+++ b/test/dbo/DboTest.C
@@ -30,6 +30,7 @@ class A;
 class B;
 class C;
 class D;
+class E;
 
 bool fractionalSeconds = true;
 
@@ -83,6 +84,7 @@ struct DboFixture : DboFixtureBase
     session_->mapClass<B>(SCHEMA "table_b");
     session_->mapClass<C>(SCHEMA "table_c");
     session_->mapClass<D>(SCHEMA "table_d");
+    session_->mapClass<E>(SCHEMA "table_e");
 
     try {
       session_->dropTables();
@@ -111,6 +113,12 @@ struct dbo_traits<D> : public dbo_default_traits
   static const char *versionField() { return 0; }
 };
 
+template<>
+struct dbo_traits<E> : public dbo_default_traits
+{
+  static const char *versionField() { return 0; }
+};
+
   }
 }
 
@@ -400,6 +408,27 @@ public:
   }
 };
 
+class E {
+public:
+  std::string name;
+
+  E() { }
+
+  E(const std::string& aName)
+    : name(aName)
+  { }
+
+  bool operator== (const E& other) const {
+    return name == other.name;
+  }
+
+  template <class Action>
+  void persist(Action& a)
+  {
+    dbo::field(a, name, "name", 1000);
+  }
+};
+
 BOOST_AUTO_TEST_CASE( dbo_test1 )
 {
   DboFixture f;
@@ -1953,3 +1982,75 @@ BOOST_AUTO_TEST_CASE( dbo_test22d )
   }
 #endif //POSTGRES
 }
+
+BOOST_AUTO_TEST_CASE( dbo_test23 )
+{
+  DboFixture f;
+  dbo::Session *session_ = f.session_;
+  {
+    dbo::Transaction t(*session_);
+
+    dbo::ptr<E> e1(new E("e1"));
+    dbo::ptr<C> c1(new C("c1"));
+    session_->add(e1);
+    session_->add(c1);
+
+    /*
+     * Note: E has no versionField
+     */
+    typedef dbo::ptr_tuple<E, C>::type EC;
+    EC ec = session_->query< EC >
+      ("select E, C from \"table_e\" E, \"table_c\" C");
+
+    BOOST_REQUIRE(ec.get<0>()->name == "e1");
+    BOOST_REQUIRE(ec.get<1>()->name == "c1");
+  }
+}
+
+BOOST_AUTO_TEST_CASE( dbo_test24 )
+{
+  DboFixture f;
+  dbo::Session *session_ = f.session_;
+  {
+    dbo::Transaction t(*session_);
+
+#ifdef POSTGRES
+    dbo::ptr<E> e1(new E("e1"));
+    session_->add(e1);
+
+    /*
+     * Note: E has no versionField, right join will result in E1 as null
+     */
+    typedef dbo::ptr_tuple<E, E>::type EE;
+    EE ee = session_->query< EE >
+      ("select \"E1\", \"E2\" from \"table_e\" \"E1\" "
+       "right join \"table_e\" \"E2\" on \"E1\".id != \"E2\".id");
+
+    BOOST_REQUIRE(ee.get<1>()->name == "e1");
+#endif //POSTGRES
+  }
+}
+
+BOOST_AUTO_TEST_CASE( dbo_test25 )
+{
+  DboFixture f;
+  dbo::Session *session_ = f.session_;
+  {
+    dbo::Transaction t(*session_);
+
+    dbo::ptr<D> d1(new D(Coordinate(42, 43), "d1"));
+    dbo::ptr<C> c1(new C("c1"));
+    session_->add(d1);
+    session_->add(c1);
+
+    /*
+     * Note: D has no versionField
+     */
+    typedef dbo::ptr_tuple<D, C>::type DC;
+    DC dc = session_->query< DC >
+      ("select D, C from \"table_d\" D, \"table_c\" C");
+
+    BOOST_REQUIRE(dc.get<0>()->name == "d1");
+    BOOST_REQUIRE(dc.get<1>()->name == "c1");
+  }
+}
-- 
1.9.1

