From 707ce58a33d02705b4fdf050bb6a329514ddad9b Mon Sep 17 00:00:00 2001
From: Bruce Toll <btoll@dhsus.com>
Date: Sat, 2 Aug 2014 16:23:52 -0400
Subject: [PATCH] Add dbo session loadLazy method to public API.

The loadLazy method can be an efficient mechanism for persisting dbo
objects without unnecessary database retrievals for some use cases.

This patch places loadLazy method in the public interface and includes
documentation. It also provides a new test case for DboTest2.C
that demonstrates the use of loadLazy along with some of its
alternatives. The output from dbo2_test2 shows the database accesses for
each alternative.
---
 src/Wt/Dbo/Session  |  22 +++++++-
 test/dbo/DboTest2.C | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 175 insertions(+), 2 deletions(-)

diff --git a/src/Wt/Dbo/Session b/src/Wt/Dbo/Session
index ed387c5..eb5cbd5 100644
--- a/src/Wt/Dbo/Session
+++ b/src/Wt/Dbo/Session
@@ -157,11 +157,30 @@ public:
    *
    * Throws an ObjectNotFoundException when the object was not found.
    *
-   * \sa ptr::id()
+   * \sa ptr::id(), loadLazy()
    */
   template <class C> ptr<C> load(const typename dbo_traits<C>::IdType& id,
 				 bool forceReread = false);
 
+  /*! \brief Lazy loads a persisted object.
+   *
+   * This method returns a database object with the given object id
+   * without directly accessing the database.
+   *
+   * If the object data is already available in the session, then it will
+   * be available upon return. Otherwise, the object data will be retrieved
+   * from the database on first access. Note: This will result in an
+   * ObjectNotFoundException if the object id is not valid.
+   *
+   * lazyLoad can be used to obtain a ptr<C> from a known id when a ptr<C>
+   * is required, but access to the C object is not anticipated. For
+   * instance, a ptr<C> may be required to add an object of class X that
+   * is in a belongsTo relationship with C.
+   *
+   * \sa ptr::id(), load()
+   */
+  template <class C> ptr<C> loadLazy(const typename dbo_traits<C>::IdType& id);
+
 #ifndef DOXYGEN_ONLY
   template <class C>
     Query< ptr<C> > find(const std::string& condition = std::string()) {
@@ -559,7 +578,6 @@ private:
 
   template <class C> Mapping<C> *getMapping() const;
   MappingInfo *getMapping(const char *tableName) const;
-  template <class C> ptr<C> loadLazy(const typename dbo_traits<C>::IdType& id);
   template <class C> ptr<C> load(SqlStatement *statement, int& column);
 
   template <class C>
diff --git a/test/dbo/DboTest2.C b/test/dbo/DboTest2.C
index 3778ced..9f5c412 100644
--- a/test/dbo/DboTest2.C
+++ b/test/dbo/DboTest2.C
@@ -307,3 +307,158 @@ BOOST_AUTO_TEST_CASE( dbo2_test1 )
   }
 
 }
+
+/*
+ * Add posts with identical content to all user blogs, using various methods.
+ */
+
+BOOST_AUTO_TEST_CASE( dbo2_test2 )
+{
+  Dbo2Fixture f;
+
+  dbo::Session& session = *f.session_;
+
+  typedef dbo::collection< dbo::ptr<User> > Users;
+
+  Post commonPost;
+  commonPost.contents = "Groupthink";
+  const size_t USER_COUNT = 2;
+
+  {
+    dbo::Transaction transaction(session);
+
+    for (int i = 0; i < USER_COUNT; i++) {
+      User *user = new User();
+      std::ostringstream os;
+      os << "User" << i;
+      user->name = os.str();
+      user->password = "Secret";
+      user->role = User::Visitor;
+      user->karma = i;
+      session.add(user);
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    commonPost.title = "Post 1";
+    std::cerr << "\nAdding '" << commonPost.title << "' to " << USER_COUNT
+      << " user blogs iterating over user objects" << std::endl;
+
+    /*
+     * Complete user objects are retrieved from DB, although only the id is
+     * needed. This could be costly with a more complex User record.
+     */
+
+    Users allUsers = session.find<User > ();
+    for (Users::const_iterator i = allUsers.begin();
+      i != allUsers.end(); ++i) {
+      dbo::ptr<User> user = *i;
+
+      commonPost.user = user;
+      dbo::ptr<Post> post = session.add(new Post(commonPost));
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    commonPost.title = "Post 2";
+    std::cerr << "\nAdding '" << commonPost.title << "' to " << USER_COUNT
+      << " user blogs using dbo::ptr link from existing posts" << std::endl;
+
+    /*
+     * If there is an existing dbo::ptr to a user, we can add new Posts
+     * without retrieving the associated user by copying the dbo::ptr.
+     * NOTE: This works because we have a single post, "Post 1", for each user.
+     */
+
+    Posts existingPosts = session.find<Post > ();
+
+    for (Posts::const_iterator i = existingPosts.begin();
+      i != existingPosts.end(); ++i) {
+
+      commonPost.user = (*i)->user;
+      dbo::ptr<Post> post = session.add(new Post(commonPost));
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    commonPost.title = "Post 3";
+    std::cerr << "\nAdding '" << commonPost.title << "' to " << USER_COUNT
+      << " user blogs using insert through user objects" << std::endl;
+
+    /*
+     * With insert(), the full User object is retrieved for each post,
+     * even though only the id is required. The User object is also
+     * updated (incremented version number).
+     */
+
+    Users allUsers = session.find<User > ();
+    for (Users::const_iterator i = allUsers.begin();
+      i != allUsers.end(); ++i) {
+      i->modify()->posts.insert(new Post(commonPost));
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    commonPost.title = "Post 4";
+    std::cerr << "\nAdding '" << commonPost.title << "' to " << USER_COUNT
+      << " user blogs using load to retrieve user objects" << std::endl;
+
+    typedef dbo::dbo_traits<User>::IdType UserId;
+    typedef dbo::collection< UserId > UserIds;
+
+    /*
+     * With load(), the full User object is retrieved for each post,
+     * even though only the id is required.
+     */
+
+    UserIds allUserIds = session.query<UserId > ("select u.\"id\" from \"user\" u");
+    for (UserIds::const_iterator i = allUserIds.begin();
+      i != allUserIds.end(); ++i) {
+      commonPost.user = session.load<User>(*i);
+      dbo::ptr<Post> post = session.add(new Post(commonPost));
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    commonPost.title = "Post 5";
+    std::cerr << "\nAdding '" << commonPost.title << "' to " << USER_COUNT
+      << " user blogs using loadLazy to retrieve user objects" << std::endl;
+
+    typedef dbo::dbo_traits<User>::IdType UserId;
+    typedef dbo::collection< UserId > UserIds;
+
+    /*
+     * With loadLazy(), there is no need to retrieve the full User object
+     * in order to obtain a dbo::ptr to the User for adding posts.
+     */
+
+    UserIds allUserIds = session.query<UserId > ("select u.\"id\" from \"user\" u");
+    for (UserIds::const_iterator i = allUserIds.begin();
+      i != allUserIds.end(); ++i) {
+      commonPost.user = session.loadLazy<User>(*i);
+      dbo::ptr<Post> post = session.add(new Post(commonPost));
+    }
+  }
+
+  {
+    dbo::Transaction transaction(session);
+    std::cerr << std::endl;
+
+    /*
+     * Each user should now have 5 posts
+     */
+
+    Users allUsers = session.find<User > ();
+    for (Users::const_iterator i = allUsers.begin();
+      i != allUsers.end(); ++i) {
+      dbo::ptr<User> user = *i;
+
+      BOOST_REQUIRE(user->posts.size() == 5);
+    }
+  }
+}
-- 
1.9.1

