From f70e3b4917826035f6205d0865e36e036cdd9b8c Mon Sep 17 00:00:00 2001
From: Bruce Toll <btoll@dhsus.com>
Date: Sat, 18 Jan 2014 16:15:38 -0500
Subject: [PATCH] Dbo test for left join & uninitialized value fix.

Added dbo_test4c to check Dbo behavior with LEFT JOIN queries. This can
result in NULL records. In the test, no exceptions are thrown, but an
unitialized variable (id) is currently referenced in Session_impl.h.

An initializer was added for id so that a single instance will be mapped
for all null records for a Dbo class in a session.

However, if LEFT JOIN queries are not supported, it would probably make
more sense to check the return code of getResult and throw an exception
if the id field is NULL.
---
 src/Wt/Dbo/Session_impl.h |  11 ++++-
 test/dbo/DboTest.C        | 111 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+), 2 deletions(-)

diff --git a/src/Wt/Dbo/Session_impl.h b/src/Wt/Dbo/Session_impl.h
index 098e2ab..e888424 100644
--- a/src/Wt/Dbo/Session_impl.h
+++ b/src/Wt/Dbo/Session_impl.h
@@ -141,9 +141,16 @@ ptr<C> Session::loadWithLongLongId(SqlStatement *statement, int& column)
      * If not, then we need to first read the object, get the id, and if
      * we already had it, delete the redundant copy.
      */
-    long long id;
+    long long id = -1L;
 
-    /* Auto-generated surrogate key is first field */
+    /*
+     * Auto-generated surrogate key is first field
+     *
+     * NOTE: getResult will return false if the id field is NULL. This
+     * could occur with a LEFT JOIN involving the table. See: dbo_test4c.
+     * In this case, the id will default to -1 and get associated with a
+     * single null record in the mapping->registry_.
+     */
     statement->getResult(column++, &id);
 
     typename Mapping<C>::Registry::iterator i = mapping->registry_.find(id);
diff --git a/test/dbo/DboTest.C b/test/dbo/DboTest.C
index 8a5fe35..82f7fe4 100644
--- a/test/dbo/DboTest.C
+++ b/test/dbo/DboTest.C
@@ -952,6 +952,117 @@ BOOST_AUTO_TEST_CASE( dbo_test4b )
   }
 }
 
+BOOST_AUTO_TEST_CASE( dbo_test4c )
+{
+  DboFixture f;
+
+  dbo::Session *session_ = f.session_;
+
+  {
+    dbo::Transaction t(*session_);
+
+    dbo::ptr<A> a1(new A());
+    dbo::ptr<A> a2(new A());
+    dbo::ptr<A> a3(new A());
+    dbo::ptr<A> a4(new A());
+
+    dbo::ptr<B> b1(new B());
+    dbo::ptr<B> b2(new B());
+
+    dbo::ptr<C> c1(new C());
+    dbo::ptr<C> c2(new C());
+
+    a1.modify()->wstring = "a1";
+    a2.modify()->wstring = "a2";
+    a3.modify()->wstring = "a3";
+    a4.modify()->wstring = "a4";
+
+    b1.modify()->name = "b1";
+    b2.modify()->name = "b2";
+
+    c1.modify()->name = "c1";
+    c2.modify()->name = "c2";
+
+    a1.modify()->b = b1;
+    a1.modify()->c = c1;
+
+    // a2 has no c
+    a2.modify()->b = b2;
+
+    // a3 has no b
+    a3.modify()->c = c2;
+
+    // a4 has no b or c
+
+    session_->add(b1);
+    session_->add(b2);
+    session_->add(c1);
+    session_->add(c2);
+    session_->add(a1);
+    session_->add(a2);
+    session_->add(a3);
+    session_->add(a4);
+  }
+
+  {
+    dbo::Transaction t(*session_);
+
+    typedef dbo::ptr_tuple<A, B, C>::type ABC;
+    typedef dbo::collection<ABC> C_ABCs;
+    typedef std::vector<ABC> ABCs;
+
+#if !defined(FIREBIRD) && !defined(MYSQL)
+    dbo::Query<ABC> q = session_->query<ABC>
+      ("select A, B, C "
+       "from \"table_a\" A "
+       "left join \"table_b\" as B on A.\"b_id\" = B.\"id\" "
+       "left join \"table_c\" as C on A.\"table_c_id\" = C.\"id\"")
+      .orderBy("A.\"wstring\"");
+
+    C_ABCs c_abcs = q.resultList();
+    ABCs abcs(c_abcs.begin(), c_abcs.end());
+
+    BOOST_REQUIRE(abcs.size() == 4);
+
+    int ii = 0;
+    for (ABCs::const_iterator i = abcs.begin(); i != abcs.end(); ++i) {
+      dbo::ptr<A> a_result;
+      dbo::ptr<B> b_result;
+      dbo::ptr<C> c_result;
+      boost::tie(a_result, b_result, c_result) = *i;
+
+      switch (ii)
+      {
+        case 0:
+            BOOST_REQUIRE(a_result->wstring == "a1");
+            BOOST_REQUIRE(b_result->name == "b1");
+            BOOST_REQUIRE(c_result->name == "c1");
+            break;
+        case 1:
+            BOOST_REQUIRE(a_result->wstring == "a2");
+            BOOST_REQUIRE(b_result->name == "b2");
+            BOOST_REQUIRE(!a_result->c);
+            break;
+        case 2:
+            BOOST_REQUIRE(a_result->wstring == "a3");
+            BOOST_REQUIRE(!a_result->b);
+            BOOST_REQUIRE(c_result->name == "c2");
+            break;
+        case 3:
+            BOOST_REQUIRE(a_result->wstring == "a4");
+            BOOST_REQUIRE(!a_result->b);
+            BOOST_REQUIRE(!a_result->c);
+            break;
+      }
+
+      ++ii;
+    }
+
+    BOOST_REQUIRE(ii == 4);
+#endif //FIREBIRD && MYSQL
+  }
+}
+
 BOOST_AUTO_TEST_CASE( dbo_test5 )
 {
   DboFixture f;
-- 
1.8.5.2

