1 // This may look like C code, but it's really -*- C++ -*-
2 /*
3  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
4  *
5  * See the LICENSE file for terms of use.
6  */
7 #ifndef WT_DBO_SESSION_H_
8 #define WT_DBO_SESSION_H_
9 
10 #include <map>
11 #include <set>
12 #include <string>
13 #include <typeinfo>
14 
15 #include <Wt/Dbo/ptr.h>
16 #include <Wt/Dbo/Field.h>
17 #include <Wt/Dbo/Query.h>
18 #include <Wt/Dbo/Transaction.h>
19 #include <Wt/Dbo/SqlConnection.h>
20 
21 namespace Wt {
22   namespace Dbo {
23     namespace Impl {
24       struct MetaDboBaseSet;
25 
26       extern WTDBO_API std::string quoteSchemaDot(const std::string& table);
27       template <class C, typename T> struct LoadHelper;
28 
29       struct WTDBO_API SetInfo {
30 	enum SetInfoFlags {
31 	  // Normally, if there is no surrogate key, the field name of the natural id
32 	  // is appended to joinSelfId/joinOtherId. These flags prevent that.
33 	  LiteralSelfId = 0x1, // joinSelfId is literal, don't append primary key name
34 	  LiteralOtherId = 0x2 // joinOtherId is literal, don't append primary key name
35 	};
36 
37 	const char *tableName;
38 	std::string joinName;
39 	std::string joinSelfId, joinOtherId;
40 	int flags;
41 	RelationType type;
42 	int fkConstraints, otherFkConstraints;
43 
44 	SetInfo(const char *aTableName, RelationType type,
45 		const std::string& aJoinName,
46 		const std::string& aJoinSelfId,
47 		int someFkConstraints);
48       };
49 
50       struct WTDBO_API MappingInfo {
51 	bool initialized_;
52 	const char *tableName;
53 	const char *versionFieldName;
54 	const char *surrogateIdFieldName;
55 
56 	std::string naturalIdFieldName; // for non-auto generated id
57 	int naturalIdFieldSize;         // for non-auto generated id
58 
59 	std::string idCondition;
60 
61 	std::vector<FieldInfo> fields;
62 	std::vector<SetInfo> sets;
63 
64 	std::vector<std::string> statements;
65 
66 	MappingInfo();
67 	virtual ~MappingInfo();
68 	virtual void init(Session& session);
69 	virtual void dropTable(Session& session,
70 			       std::set<std::string>& tablesDropped);
71 	virtual void rereadAll();
72 	virtual MetaDboBase *create(Session& session);
73 	virtual void load(Session& session, MetaDboBase *obj);
74 	virtual MetaDboBase *load(Session& session, SqlStatement *statement,
75 				  int& column);
76 
77 	std::string primaryKeys() const;
78       };
79     }
80 
81 struct Null {
82   static Null null_;
83 };
84 
85 /*! \brief Enumeration that indicates the flush mode.
86  *
87  * \sa setFlushMode(), discardUnflushed()
88  */
89 enum class FlushMode {
90   Auto,    //!< Dbo decides when to flush changes to a transaction
91   Manual   //!< Changes are never automatically flushed
92 };
93 
94 class Call;
95 //class SqlConnection;
96 class SqlConnectionPool;
97 class SqlStatement;
98 template <typename Result, typename BindStrategy> class Query;
99 struct DirectBinding;
100 struct DynamicBinding;
101 
102 /*! \class Session Wt/Dbo/Session.h Wt/Dbo/Session.h
103  *  \brief A database session.
104  *
105  * A database session manages meta data about the mapping of C++
106  * classes to database tables, and keeps track of a working set of
107  * in-memory objects (objects which are referenced from your code or
108  * from within a transaction).
109  *
110  * It also manages an active transaction, which you need to access
111  * database objects.
112  *
113  * You can provide the session with a dedicated database connection
114  * using setConnection(), or with a connection pool (from which it
115  * will take a connection while processing a transaction) using
116  * setConnectionPool().
117  *
118  * A session will typically be a long-lived object in your
119  * application.
120  *
121  * \ingroup dbo
122  */
123 class WTDBO_API Session
124 {
125 public:
126   /*! \brief Creates a database session.
127    */
128   Session();
129 
130   /*! \brief Destructor.
131    *
132    * A session must survive all database objects that have been loaded
133    * through it, and will warning during this destructor if there are
134    * still database objects that are being referenced from a ptr.
135    */
136   virtual ~Session();
137 
138   // Sessions are not copyable
139   Session(const Session &) = delete;
140   Session& operator=(const Session &) = delete;
141 
142   // Sessions are not movable
143   Session(Session &&) = delete;
144   Session& operator=(Session &&) = delete;
145 
146   /*! \brief Sets a dedicated connection.
147    *
148    * The connection will be used exclusively by this session.
149    *
150    * \sa setConnectionPool()
151    */
152   void setConnection(std::unique_ptr<SqlConnection> connection);
153 
154   /*! \brief Sets a connection pool.
155    *
156    * The connection pool is typically shared with other sessions.
157    *
158    * \sa setConnection()
159    */
160   void setConnectionPool(SqlConnectionPool& pool);
161 
162   /*! \brief Maps a class to a database table.
163    *
164    * The class \p C is mapped to table with name \p tableName. You
165    * need to map classes to tables.
166    *
167    * You may provide a schema-qualified table name, if the underlying
168    * database supports this, eg. <tt>"myschema.users"</tt>.
169    */
170   template <class C> void mapClass(const char *tableName);
171 
172   /*! \brief Returns the mapped table name for a class.
173    *
174    * \sa mapClass(), tableNameQuoted()
175    */
176   template <class C> const char *tableName() const;
177 
178   /*! \brief Returns the mapped quoted table name for a class.
179    *
180    * This will quote schemas, as necessary.
181    *
182    * \sa mapClass(), tableName()
183    */
184   template <class C> const std::string tableNameQuoted() const;
185 
186   /*! \brief Persists a transient object.
187    *
188    * The transient object pointed to by \p ptr is added to the
189    * session, and will be persisted when the session is flushed.
190    *
191    * A transient object is usually a newly created object which want
192    * to add to the database.
193    *
194    * The method returns \p ptr.
195    */
196   template <class C> ptr<C> add(ptr<C>& ptr);
197 
198   /*! \brief Persists a transient object.
199    *
200    * This is an overloaded method for convenience, and is implemented as:
201    * \code
202    * return add(ptr<C>(std::move(obj)));
203    * \endcode
204    *
205    * The method returns a database pointer to the object.
206    */
207   template <class C> ptr<C> add(std::unique_ptr<C> obj);
208 
209   /*! \brief Persists a transient object.
210    *
211    * This an overloaded method for convenience, and is implemented as:
212    * \code
213    * return add(ptr<C>(std::unique_ptr<T>(new T(...))))
214    * \endcode
215    *
216    * \sa Wt::Dbo::make_ptr()
217    */
218   template<typename T, typename ...Args>
addNew(Args &&...args)219   ptr<T> addNew( Args&& ...args )
220   {
221     return add(std::unique_ptr<T>(new T(std::forward<Args>(args)...)));
222   }
223 
224   /*! \brief Loads a persisted object.
225    *
226    * This method returns a database object with the given object
227    * id. If the object was already loaded in the session, the loaded
228    * object is returned, otherwise the object is loaded from the
229    * database.
230    *
231    * If \p forceReread is set to \c true, then a fresh copy is loaded
232    * from the database. This is almost equivalent to calling \link
233    * ptr<C>::reread() reread()\endlink on the returned object, except
234    * that it will not result in two database reads in case the object was
235    * in fact not yet loaded in the session.
236    *
237    * Throws an ObjectNotFoundException when the object was not found.
238    *
239    * \sa ptr::id(), loadLazy()
240    */
241   template <class C> ptr<C> load(const typename dbo_traits<C>::IdType& id,
242 				 bool forceReread = false);
243 
244   /*! \brief Lazy loads a persisted object.
245    *
246    * This method returns a database object with the given object id
247    * without directly accessing the database.
248    *
249    * If the object data is already available in the session, then it will
250    * be available upon return. Otherwise, the object data will be retrieved
251    * from the database on first access. Note: This will result in an
252    * ObjectNotFoundException if the object id is not valid.
253    *
254    * lazyLoad can be used to obtain a ptr<C> from a known id when a ptr<C>
255    * is required, but access to the C object is not anticipated. For
256    * instance, a ptr<C> may be required to add an object of class X that
257    * is in a belongsTo relationship with C.
258    *
259    * \sa ptr::id(), load()
260    */
261   template <class C> ptr<C> loadLazy(const typename dbo_traits<C>::IdType& id);
262 
263 #ifndef DOXYGEN_ONLY
264   template <class C>
265     Query< ptr<C> > find(const std::string& condition = std::string()) {
266     // implemented in-line because otherwise it crashes gcc 4.0.1
267     return find<C, DynamicBinding>(condition);
268   }
269 #endif // DOXYGEN_ONLY
270 
271   /*! \brief Finds database objects.
272    *
273    * This method creates a query for finding objects of type \p C.
274    *
275    * When passing an empty \p condition parameter, it will return all
276    * objects of type \p C. Otherwise, it will add the condition, by
277    * generating an SQL <i>where</i> clause.
278    *
279    * The \p BindStrategy specifies how you want to bind parameters to
280    * your query (if any).
281    *
282    * When using \p DynamicBinding (which is the default), you will
283    * defer the binding until the query is run. This has the advantage
284    * that you can compose the query definition using helper methods
285    * provided in the query object, you can keep the query around and
286    * run the query multiple times, perhaps with different parameter
287    * values or to scroll through the query results.
288    *
289    * When using \p DirectBinding, the query must be specified entirely
290    * using the \p condition, and can be run only once. This method
291    * does have the benefit of binding parameters directly to the
292    * underlying prepared statement.
293    *
294    * This method is convenient when you are querying only results from a
295    * single table. For more generic query support, see query().
296    *
297    * Usage example:
298    * \code
299    * // Bart is missing, let's find him.
300    * Wt::Dbo::ptr<User> bart = session.find<User>().where("name = ?").bind("Bart");
301    *
302    * // Find all users, order by name
303    * typedef Wt::Dbo::collection< Wt::Dbo::ptr<User> > Users;
304    * Users users = session.find<User>().orderBy("name");
305    * \endcode
306    *
307    * In the \p condition, parameters can be bound using '?' as a
308    * positional placeholder: each occurence of '?' (as a lexical
309    * token) is replaced by a bound parameter. This is actually done by
310    * most of the backends themselves using prepared statements and
311    * parameter binding. Parameter binding is possible for all types
312    * for which sql_value_traits is specialized.
313    *
314    * \sa query()
315    */
316 #ifdef DOXYGEN_ONLY
317   template <class C, typename BindStrategy = DynamicBinding>
318 #else
319   template <class C, typename BindStrategy>
320 #endif
321     Query< ptr<C>, BindStrategy>
322     find(const std::string& condition = std::string());
323 
324 #ifndef DOXYGEN_ONLY
325   template <class Result> Query<Result> query(const std::string& sql);
326 #endif // DOXYGEN_ONLY
327 
328   /*! \brief Creates a query.
329    *
330    * The sql statement should be a complete SQL statement, starting
331    * with a "select ". The items listed in the "select" must match the
332    * \p Result type. An item that corresponds to a database object
333    * (ptr) is substituted with the selection of all the fields in the
334    * dbo.
335    *
336    * For example, the following query (class User is mapped onto table 'user'):
337    * \code
338    * session.query< ptr<User> >("select u from user u").where("u.name = ?").bind("Bart");
339    * \endcode
340    * is the more general version of:
341    * \code
342    * session.find<User>().where("name = ?").bind("Bart");
343    * \endcode
344    *
345    * Note that "u" in this query will be expanded to select the fields of the
346    * user table (u.id, u.version, u.name, ...). The same expansion happens when
347    * using an alias in Query::groupBy().
348    *
349    * The additional flexibility offered by %query() over find() is
350    * however that it may support other result types.
351    *
352    * Thus, it may return plain values:
353    * \code
354    * session.query<int>("select count(1) from ...");
355    * \endcode
356    *
357    * Or std::tuple for an arbitrary combination of result values:
358    *
359    * \code
360    * session.query< std::tuple<int, int> >("select A.id, B.id from table_a A, table_b B").where("...");
361    * \endcode
362    *
363    * A tuple may combine any kind of object that is supported as a result,
364    * including database objects (see also ptr_tuple):
365    * \code
366    * session.query< std::tuple<ptr<A>, ptr<B> > >("select A, B from table_a A, table_b B").where("...");
367    * \endcode
368    *
369    * The \p BindStrategy specifies how you want to bind parameters to
370    * your query (if any).
371    *
372    * When using \p DynamicBinding (which is the default), you will
373    * defer the binding until the query is run. This has the advantage
374    * that you can compose the query using helper methods provided in
375    * the Query object, you can keep the query around and run the query
376    * multiple times, perhaps with different parameter values or to
377    * scroll through the query results.
378    *
379    * When using \p DirectBinding, the query must be specified entirely
380    * using the \p sql, and can be run only once. This method does have
381    * the benefit of binding parameters directly to the underlying
382    * prepared statement.
383    *
384    * This method uses query_result_traits to unmarshal the query result
385    * into the \p Result type.
386    *
387    * In the \p sql query, parameters can be bound using '?' as the
388    * positional placeholder: each occurence of '?' (as a lexical
389    * token) is replaced by a bound parameter. This is actually done by
390    * most of the backends themselves using prepared statements and
391    * parameter binding. Parameter binding is possible for all types
392    * for which sql_value_traits is specialized.
393    *
394    * \note PostgreSQL uses a literal question mark ('?') as an operator. To
395    * distinguish between a positional placeholder and a literal '?', use a
396    * double question mark ('??') if you mean a literal '?'.
397    *
398    * \note The query must be a ASCII-7 string: UTF-8 is not supported by
399    * the underlying query parser. To add a non-English string to the query
400    * use parameter binding instead (which prevents against SQL injection
401    * attacks at the same time) instead of string concatenation.
402    */
403 #ifdef DOXYGEN_ONLY
404   template <class Result, typename BindStrategy = DynamicBinding>
405 #else
406   template <class Result, typename BindStrategy>
407 #endif
408     Query<Result, BindStrategy> query(const std::string& sql);
409 
410   /*! \brief Executs an Sql command.
411    *
412    * This executs an Sql command. It differs from query() in that no
413    * result is expected from the call.
414    *
415    * Usage example:
416    * \code
417    * session.execute("update user set name = ? where name = ?").bind("Bart").bind("Sarah");
418    * \endcode
419    */
420   Call execute(const std::string& sql);
421 
422   /*! \brief Creates the database schema.
423    *
424    * This will create the database schema of the mapped tables. Schema
425    * creation will fail if one or more tables already existed. The creation
426    * of the tables is executed in a transaction that is rolled back when
427    * an error occurs.
428    *
429    * This method throws an Wt::Dbo::Exception if the table creation failed.
430    *
431    * \sa mapClass(), dropTables()
432    */
433   void createTables();
434 
435   /*! \brief Returns database creation SQL.
436    */
437   std::string tableCreationSql();
438 
439   /*! \brief Drops the database schema.
440    *
441    * This will drop the database schema. Dropping the schema will fail
442    * if one or more tables did not exist.
443    *
444    * \sa createTables()
445    */
446   void dropTables();
447 
448   /*! \brief Flushes the session.
449    *
450    * This flushes all modified objects to the database. This does not
451    * commit the transaction.
452    *
453    * Normally, you need not to call this method as the session is
454    * flushed automatically before committing a transaction, or before
455    * running a query (to be sure to take into account pending
456    * modifications).
457    */
458   void flush();
459 
460   /*! \brief Rereads all objects.
461    *
462    * This rereads all objects from the database, possibly discarding
463    * unflushed modifications. This is a catch-all solution for a
464    * StaleObjectException.
465    *
466    * If a \p tableName is given, then only objects of that table are
467    * reread.
468    *
469    * \sa ptr::reread()
470    */
471   void rereadAll(const char *tableName = nullptr);
472 
473   /*! \brief Discards all unflushed changes.
474    *
475    * This method is useful when the flushMode() is set to Manual. It discards
476    * all Dbo-objects which were added to the session and rereads all existing
477    * Dbo-objects.
478    *
479    * \sa setFlushMode()
480    */
481   void discardUnflushed();
482 
483   void getFields(const char *tableName, std::vector<FieldInfo>& result);
484 
485   /*! \brief Returns the flushMode.
486    *
487    * \sa setFlushMode()
488    */
flushMode()489   FlushMode flushMode() { return flushMode_; }
490 
491   /*! \brief Sets the flushMode.
492    *
493    * The default flushMode is Auto. LabelOption::Inside a transaction this means that
494    * changes are flushed when a query is affected by them. When flushMode is
495    * set to Manual, changes are only flushed when the user manually calls
496    * flush(), or resets the mode to Auto. Query's wil possibly return an
497    * inconsistent result, but collections will still keep track of changes.
498    * This also makes it possible to operate on Dbo-objects and collections
499    * outside of a transaction. When the moment comes to flush the changes, a
500    * transaction must of course be active.
501    *
502    * <em>Note:</em> only operations on a collecion are tracked in Manual mode,
503    * reciproke operations are not yet taken into account.
504    *
505    * \sa flushMode(), discardUnflushed()
506    */
setFlushMode(FlushMode mode)507   void setFlushMode(FlushMode mode) { flush(); flushMode_ = mode; }
508 
509 private:
510   mutable std::string longlongType_;
511   mutable std::string intType_;
512   mutable bool haveSupportUpdateCascade_;
513 
514   enum { SqlInsert = 0,
515 	 SqlUpdate = 1,
516 	 SqlDelete = 2,
517 	 SqlDeleteVersioned = 3,
518 	 SqlSelectById = 4,
519 	 FirstSqlSelectSet = 5 };
520 
521   struct JoinId {
522     std::string joinIdName;
523     std::string tableIdName;
524     std::string sqlType;
525 
526     JoinId(const std::string& aJoinIdName,
527 	   const std::string& aTableIdName,
528 	   const std::string& aSqlType);
529   };
530 
531   template <class C>
532   struct Mapping : public Impl::MappingInfo
533   {
534     typedef std::map<typename dbo_traits<C>::IdType, MetaDbo<C> *> Registry;
535     Registry registry_;
536 
537     virtual ~Mapping();
538     virtual void init(Session& session) override;
539     virtual void dropTable(Session& session,
540 			   std::set<std::string>& tablesDropped) override;
541     virtual void rereadAll() override;
542     virtual MetaDbo<C> *create(Session& session) override;
543     virtual void load(Session& session, MetaDboBase *obj) override;
544     virtual MetaDbo<C> *load(Session& session, SqlStatement *statement,
545 			     int& column) override;
546   };
547 
548   typedef const std::type_info * const_typeinfo_ptr;
549   struct typecomp {
operatortypecomp550     bool operator() (const const_typeinfo_ptr& lhs, const const_typeinfo_ptr& rhs) const { return lhs->before(*rhs) != 0;
551 	}
552   };
553 
554   typedef std::map<const_typeinfo_ptr,
555 		   Impl::MappingInfo *, typecomp> ClassRegistry;
556   typedef std::map<std::string, Impl::MappingInfo *> TableRegistry;
557 
558   ClassRegistry classRegistry_;
559   TableRegistry tableRegistry_;
560   bool schemaInitialized_;
561   mutable LimitQuery limitQueryMethod_;
562   mutable bool requireSubqueryAlias_;
563 
564   Impl::MetaDboBaseSet *dirtyObjects_;
565   std::vector<MetaDboBase*> objectsToAdd_;
566   std::unique_ptr<SqlConnection> connection_;
567   SqlConnectionPool *connectionPool_;
568   Transaction::Impl *transaction_;
569   FlushMode flushMode_;
570 
571   void initSchema() const;
572   void resolveJoinIds(Impl::MappingInfo *mapping);
573   void prepareStatements(Impl::MappingInfo *mapping);
574 
575   void executeSql(std::vector<std::string> &sql, std::ostream *sout);
576   void executeSql(std::stringstream &sql, std::ostream *sout);
577   std::string constraintName(const char *tableName,
578                              std::string foreignKeyName);
579 
580   std::vector<JoinId> getJoinIds(Impl::MappingInfo *mapping,
581 				 const std::string& joinId,
582 				 bool literalJoinId);
583 
584   void createTable(Impl::MappingInfo *mapping,
585 		   std::set<std::string>& tablesCreated,
586                    std::ostream *sout,
587                    bool createConstraints);
588   void createRelations(Impl::MappingInfo *mapping,
589 		       std::set<std::string>& tablesCreated,
590 		       std::ostream *sout);
591   std::string constraintString(Impl::MappingInfo *mapping,
592                                const FieldInfo& field,
593                                unsigned fromIndex,
594                                unsigned toIndex);
595   unsigned findLastForeignKeyField(Impl::MappingInfo *mapping,
596                                    const FieldInfo& field,
597                                    unsigned index);
598   void createJoinTable(const std::string& joinName,
599 		       Impl::MappingInfo *mapping1, Impl::MappingInfo *mapping2,
600 		       const std::string& joinId1,
601 		       const std::string& joinId2,
602 		       int fkConstraints1, int fkConstraints2,
603 		       bool literalJoinId1, bool literalJoinId2,
604 		       std::set<std::string>& tablesCreated,
605 		       std::ostream *sout);
606   void addJoinTableFields(Impl::MappingInfo& joinTableMapping,
607 			  Impl::MappingInfo *mapping, const std::string& joinId,
608 			  const std::string& foreignKeyName, int fkConstraints,
609 			  bool literalJoinId);
610   void createJoinIndex(Impl::MappingInfo& joinTableMapping,
611 		       Impl::MappingInfo *mapping,
612 		       const std::string& joinId,
613 		       const std::string& foreignKeyName,
614 		       std::ostream *sout);
615 
616   void needsFlush(MetaDboBase *dbo);
617 
618   template <class C> Mapping<C> *getMapping() const;
619   Impl::MappingInfo *getMapping(const char *tableName) const;
620 
621   void load(MetaDboBase *obj);
622   template <class C> ptr<C> load(SqlStatement *statement, int& column);
623 
624   template <class C>
625     MetaDbo<C> *loadWithNaturalId(SqlStatement *statement, int& column);
626   template <class C>
627     MetaDbo<C> *loadWithLongLongId(SqlStatement *statement, int& column);
628 
629   void discardChanges(MetaDboBase *obj);
630   template <class C> void prune(MetaDbo<C> *obj);
631 
632   template<class C> void implSave(MetaDbo<C>& dbo);
633   template<class C> void implDelete(MetaDbo<C>& dbo);
634   template<class C> void implTransactionDone(MetaDbo<C>& dbo, bool success);
635   template<class C> void implLoad(MetaDbo<C>& dbo, SqlStatement *statement,
636 				  int& column);
637 
638   static std::string statementId(const char *table, int statementIdx);
639 
640   template <class C> SqlStatement *getStatement(int statementIdx);
641   SqlStatement *getStatement(const std::string& id);
642   SqlStatement *getStatement(const char *tableName, int statementIdx);
643   const std::string& getStatementSql(const char *tableName, int statementIdx);
644 
645   SqlStatement *prepareStatement(const std::string& id,
646 				 const std::string& sql);
647   SqlStatement *getOrPrepareStatement(const std::string& sql);
648 
649   template <class C> void prepareStatements();
650   template <class C> std::string manyToManyJoinId(const std::string& joinName,
651 						  const std::string& notId);
652 
653   std::unique_ptr<SqlConnection> useConnection();
654   void returnConnection(std::unique_ptr<SqlConnection> connection);
655   SqlConnection *connection(bool openTransaction);
656 
657   MetaDboBase *createDbo(Impl::MappingInfo *mapping);
658 
659   template <class C> friend class MetaDbo;
660   template <class C> friend class collection;
661   template <class C> friend class weak_ptr;
662   template <class C, typename S> friend class Query;
663   friend class AbstractQuery;
664   template <class C> friend class Impl::QueryBase;
665   template <class C, typename T> friend struct Impl::LoadHelper;
666   template <typename V> friend class FieldRef;
667   template <class C> friend struct query_result_traits;
668   template <class C> friend class SaveDbAction;
669   template <class C> friend class LoadDbAction;
670   template <class C> friend class PtrRef;
671   friend class SetReciproceAction;
672   friend class ToAnysAction;
673   friend class FromAnysAction;
674 
675   friend class Call;
676   friend class CollectionHelper;
677   friend class DboAction;
678   friend class DropSchema;
679   friend class FromAnyAction;
680   friend class InitSchema;
681   friend class LoadBaseAction;
682   friend class MetaDboBase;
683   friend class SaveBaseAction;
684   friend class SessionAddAction;
685   friend class Transaction;
686   friend class TransactionDoneAction;
687 
688   friend struct Transaction::Impl;
689 };
690 
691   }
692 }
693 
694 #endif // WT_SESSION_H_
695