1 /**
2  * HibernateD - Object-Relation Mapping for D programming language, with interface similar to Hibernate. 
3  * 
4  * Hibernate documentation can be found here:
5  * $(LINK http://hibernate.org/docs)$(BR)
6  * 
7  * Source file hibernated/session.d.
8  *
9  * This module contains implementation of Hibernated SessionFactory and Session classes.
10  * 
11  * Copyright: Copyright 2013
12  * License:   $(LINK www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
13  * Author:   Vadim Lopatin
14  */
15 module hibernated.session;
16 
17 import std.algorithm;
18 import std.conv;
19 //import std.stdio : writeln;
20 import std.exception;
21 import std.variant;
22 
23 import ddbc.core;
24 import ddbc.common;
25 
26 import hibernated.type;
27 import hibernated.dialect;
28 import hibernated.core;
29 import hibernated.metadata;
30 import hibernated.query;
31 
32 // For backwards compatibily
33 // 'enforceEx' will be removed with 2.089
34 static if(__VERSION__ < 2080) {
35     alias enforceHelper = enforceEx;
36 } else {
37     alias enforceHelper = enforce;
38 }
39 
40 // For backwards compatibily (since D 2.101, logger is no longer in std.experimental)
41 static if (__traits(compiles, (){ import std.logger; } )) {
42     pragma(msg, "Hibernated will log using 'std.logger'.");
43     import std.logger : trace;
44 } else {
45     pragma(msg, "Hibernated will log using 'std.experimental.logger'.");
46     import std.experimental.logger : trace;
47 }
48 
49 /// Factory to create HibernateD Sessions - similar to org.hibernate.SessionFactory
50 interface SessionFactory {
51     /// close all active sessions
52 	void close();
53     /// check if session factory is closed
54 	bool isClosed();
55     /// creates new session
56 	Session openSession();
57     /// retrieve information about tables and indexes for schema
58     DBInfo getDBMetaData();
59 }
60 
61 /// Session - main interface to load and persist entities -- similar to org.hibernate.Session
62 abstract class Session
63 {
64     /// returns metadata
65     EntityMetaData getMetaData();
66 
67 	/// not supported in current implementation
68 	Transaction beginTransaction();
69 	/// not supported in current implementation
70 	void cancelQuery();
71 	/// not supported in current implementation
72 	void clear();
73 
74 	/// closes session
75 	Connection close();
76 
77     ///Does this session contain any changes which must be synchronized with the database? In other words, would any DML operations be executed if we flushed this session?
78     bool isDirty();
79     /// Check if the session is still open.
80     bool isOpen();
81     /// Check if the session is currently connected.
82     bool isConnected();
83     /// Check if this instance is associated with this Session.
84     bool contains(Object object);
85     /// Retrieve session factory used to create this session
86 	SessionFactory getSessionFactory();
87     /// Lookup metadata to find entity name for object.
88 	string getEntityName(Object object);
89     /// Lookup metadata to find entity name for class type info.
90     string getEntityName(TypeInfo_Class type);
91 
92     /// Return the persistent instance of the given named entity with the given identifier, or null if there is no such persistent instance.
93     T get(T : Object, ID)(ID id) {
94         Variant v = id;
95         return cast(T)getObject(getEntityName(T.classinfo), v);
96     }
97     
98     /// Read the persistent state associated with the given identifier into the given transient instance.
99     T load(T : Object, ID)(ID id) {
100         Variant v = id;
101         return cast(T)loadObject(getEntityName(T.classinfo), v);
102     }
103 
104     /// Read the persistent state associated with the given identifier into the given transient instance.
105     void load(T : Object, ID)(T obj, ID id) {
106         Variant v = id;
107         loadObject(obj, v);
108     }
109     
110     /// Return the persistent instance of the given named entity with the given identifier, or null if there is no such persistent instance.
111 	Object getObject(string entityName, Variant id);
112 
113     /// Read the persistent state associated with the given identifier into the given transient instance.
114     Object loadObject(string entityName, Variant id);
115 
116     /// Read the persistent state associated with the given identifier into the given transient instance
117     void loadObject(Object obj, Variant id);
118 
119     /// Re-read the state of the given instance from the underlying database.
120 	void refresh(Object obj);
121 
122     /// Persist the given transient instance, first assigning a generated identifier.
123 	Variant save(Object obj);
124 
125 	/// Persist the given transient instance.
126 	void persist(Object obj);
127 
128 	/// Update the persistent instance with the identifier of the given detached instance.
129 	void update(Object object);
130 
131     /// remove object from DB (renamed from original Session.delete - it's keyword in D)
132     void remove(Object object);
133 
134 	/// Create a new instance of Query for the given HQL query string
135 	Query createQuery(string queryString);
136 }
137 
138 /// Transaction interface: TODO
139 interface Transaction {
140 }
141 
142 /// Interface for usage of HQL queries.
143 abstract class Query
144 {
145 	///Get the query string.
146 	string 	getQueryString();
147 	/// Convenience method to return a single instance that matches the query, or null if the query returns no results.
148 	Object 	uniqueObject();
149     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
150     Object  uniqueObject(Object obj);
151     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
152     T uniqueResult(T : Object)() {
153         return cast(T)uniqueObject();
154     }
155     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
156     T uniqueResult(T : Object)(T obj) {
157         return cast(T)uniqueObject(obj);
158     }
159 
160     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
161 	Variant[] uniqueRow();
162 	/// Return the query results as a List of entity objects
163 	Object[] listObjects();
164     /// Return the query results as a List of entity objects
165     T[] list(T : Object)() {
166         return cast(T[])listObjects();
167     }
168     /// Return the query results as a List which each row as Variant array
169 	Variant[][] listRows();
170 	
171 	/// Bind a value to a named query parameter (all :parameters used in query should be bound before executing query).
172 	protected Query setParameterVariant(string name, Variant val);
173 
174     /// Bind a value to a named query parameter (all :parameters used in query should be bound before executing query).
175     Query setParameter(T)(string name, T val) {
176         static if (is(T == Variant)) {
177             return setParameterVariant(name, val);
178         } else {
179             return setParameterVariant(name, Variant(val));
180         }
181     }
182 }
183 
184 
185 /// Allows reaction to basic SessionFactory occurrences
186 interface SessionFactoryObserver {
187     ///Callback to indicate that the given factory has been closed.
188     void sessionFactoryClosed(SessionFactory factory);
189     ///Callback to indicate that the given factory has been created and is now ready for use.
190     void sessionFactoryCreated(SessionFactory factory);
191 }
192 
193 interface EventListeners {
194     // TODO:
195 }
196 
197 interface ConnectionProvider {
198 }
199 
200 interface Settings {
201     Dialect getDialect();
202     ConnectionProvider getConnectionProvider();
203     bool isAutoCreateSchema();
204 }
205 
206 interface Mapping {
207     string getIdentifierPropertyName(string className);
208     Type getIdentifierType(string className);
209     Type getReferencedPropertyType(string className, string propertyName);
210 }
211 
212 class Configuration {
213     bool dummy;
214 }
215 
216 class EntityCache {
217     const string name;
218     const EntityInfo entity;
219     Object[Variant] items;
220     this(const EntityInfo entity) {
221         this.entity = entity;
222         this.name = entity.name;
223     }
224     bool hasKey(Variant key) {
225         return ((key in items) !is null);
226     }
227     Object peek(Variant key) {
228         if ((key in items) is null)
229             return null;
230         return items[key];
231     }
232     Object get(Variant key) {
233         enforceHelper!CacheException((key in items) !is null, "entity " ~ name ~ " with key " ~ key.toString() ~ " not found in cache");
234         return items[key];
235     }
236     void put(Variant key, Object obj) {
237         items[key] = obj;
238     }
239     Object[] lookup(Variant[] keys, out Variant[] unknownKeys) {
240         Variant[] unknown;
241         Object[] res;
242         foreach(key; keys) {
243             Object obj = peek(normalize(key));
244             if (obj !is null) {
245                 res ~= obj;
246             } else {
247                 unknown ~= normalize(key);
248             }
249         }
250         unknownKeys = unknown;
251         return res;
252     }
253 }
254 
255 /// helper class to disconnect Lazy loaders from closed session.
256 class SessionAccessor {
257     private SessionImpl _session;
258 
259     this(SessionImpl session) {
260         _session = session;
261     }
262     /// returns session, with session state check - throws LazyInitializationException if attempting to get unfetched lazy data while session is closed
263     SessionImpl get() {
264         enforceHelper!LazyInitializationException(_session !is null, "Cannot read from closed session");
265         return _session;
266     }
267     /// nulls session reference
268     void onSessionClosed() {
269         _session = null;
270     }
271 }
272 
273 /// Implementation of HibernateD session
274 class SessionImpl : Session {
275 
276     private bool closed;
277     SessionFactoryImpl sessionFactory;
278     private EntityMetaData metaData;
279     Dialect dialect;
280     DataSource connectionPool;
281     Connection conn;
282 
283     EntityCache[string] cache;
284 
285     private SessionAccessor _accessor;
286     @property SessionAccessor accessor() {
287         return _accessor;
288     }
289 
290     package EntityCache getCache(string entityName) {
291         EntityCache res;
292         if ((entityName in cache) is null) {
293             res = new EntityCache(metaData[entityName]);
294             cache[entityName] = res;
295         } else {
296             res = cache[entityName];
297         }
298         return res;
299     }
300 
301     package bool keyInCache(string entityName, Variant key) {
302         return getCache(entityName).hasKey(normalize(key));
303     }
304     
305     package Object peekFromCache(string entityName, Variant key) {
306         return getCache(entityName).peek(normalize(key));
307     }
308 
309     package Object getFromCache(string entityName, Variant key) {
310         return getCache(entityName).get(normalize(key));
311     }
312 
313     package void putToCache(string entityName, Variant key, Object value) {
314         return getCache(entityName).put(normalize(key), value);
315     }
316 
317     package Object[] lookupCache(string entityName, Variant[] keys, out Variant[] unknownKeys) {
318         return getCache(entityName).lookup(keys, unknownKeys);
319     }
320     
321     override EntityMetaData getMetaData() {
322         return metaData;
323     }
324 
325     private void checkClosed() {
326         enforceHelper!SessionException(!closed, "Session is closed");
327     }
328 
329     this(SessionFactoryImpl sessionFactory, EntityMetaData metaData, Dialect dialect, DataSource connectionPool) {
330         trace("Creating session");
331         this.sessionFactory = sessionFactory;
332         this.metaData = metaData;
333         this.dialect = dialect;
334         this.connectionPool = connectionPool;
335         this.conn = connectionPool.getConnection();
336         this._accessor = new SessionAccessor(this);
337     }
338 
339     override Transaction beginTransaction() {
340         throw new HibernatedException("Method not implemented");
341     }
342     override void cancelQuery() {
343         throw new HibernatedException("Method not implemented");
344     }
345     override void clear() {
346         throw new HibernatedException("Method not implemented");
347     }
348     ///Does this session contain any changes which must be synchronized with the database? In other words, would any DML operations be executed if we flushed this session?
349     override bool isDirty() {
350         throw new HibernatedException("Method not implemented");
351     }
352     /// Check if the session is still open.
353     override bool isOpen() {
354         return !closed;
355     }
356     /// Check if the session is currently connected.
357     override bool isConnected() {
358         return !closed;
359     }
360     /// End the session by releasing the JDBC connection and cleaning up.
361     override Connection close() {
362         checkClosed();
363         _accessor.onSessionClosed();
364         closed = true;
365         sessionFactory.sessionClosed(this);
366         trace("closing connection");
367         assert(conn !is null);
368         conn.close();
369         return null;
370     }
371     ///Check if this instance is associated with this Session
372     override bool contains(Object object) {
373         throw new HibernatedException("Method not implemented");
374     }
375     override SessionFactory getSessionFactory() {
376         checkClosed();
377         return sessionFactory;
378     }
379 
380     override string getEntityName(Object object) {
381         checkClosed();
382         return metaData.findEntityForObject(object).name;
383     }
384 
385     override string getEntityName(TypeInfo_Class type) {
386         checkClosed();
387         return metaData.getEntityName(type);
388     }
389 
390     override Object getObject(string entityName, Variant id) {
391         auto info = metaData.findEntity(entityName);
392         return getObject(info, null, id);
393     }
394 
395     /// Read the persistent state associated with the given identifier into the given transient instance.
396     override Object loadObject(string entityName, Variant id) {
397         Object obj = getObject(entityName, id);
398         enforceHelper!ObjectNotFoundException(obj !is null, "Entity " ~ entityName ~ " with id " ~ to!string(id) ~ " not found");
399         return obj;
400     }
401 
402     /// Read the persistent state associated with the given identifier into the given transient instance
403     override void loadObject(Object obj, Variant id) {
404         auto info = metaData.findEntityForObject(obj);
405         Object found = getObject(info, obj, id);
406         enforceHelper!ObjectNotFoundException(found !is null, "Entity " ~ info.name ~ " with id " ~ to!string(id) ~ " not found");
407     }
408 
409     /// Read the persistent state associated with the given identifier into the given transient instance
410     Object getObject(const EntityInfo info, Object obj, Variant id) {
411         string hql = "FROM " ~ info.name ~ " WHERE " ~ info.getKeyProperty().propertyName ~ "=:Id";
412         Query q = createQuery(hql).setParameter("Id", id);
413         Object res = q.uniqueResult(obj);
414         return res;
415     }
416     
417     /// Read entities referenced by property 
418     Object[] loadReferencedObjects(const EntityInfo info, string referencePropertyName, Variant fk) {
419         string hql = "SELECT a2 FROM " ~ info.name ~ " AS a1 JOIN a1." ~ referencePropertyName ~ " AS a2 WHERE a1." ~ info.getKeyProperty().propertyName ~ "=:Fk";
420         Query q = createQuery(hql).setParameter("Fk", fk);
421         Object[] res = q.listObjects();
422         return res;
423     }
424 
425     /// Re-read the state of the given instance from the underlying database.
426     override void refresh(Object obj) {
427         auto info = metaData.findEntityForObject(obj);
428         string query = metaData.generateFindByPkForEntity(dialect, info);
429         enforceHelper!TransientObjectException(info.isKeySet(obj), "Cannot refresh entity " ~ info.name ~ ": no Id specified");
430         Variant id = info.getKey(obj);
431         //trace("Finder query: " ~ query);
432         PreparedStatement stmt = conn.prepareStatement(query);
433         scope(exit) stmt.close();
434         stmt.setVariant(1, id);
435         ResultSet rs = stmt.executeQuery();
436         //trace("returned rows: " ~ to!string(rs.getFetchSize()));
437         scope(exit) rs.close();
438         if (rs.next()) {
439             //trace("reading columns");
440             metaData.readAllColumns(obj, rs, 1);
441             //trace("value: " ~ obj.toString);
442         } else {
443             // not found!
444             enforceHelper!ObjectNotFoundException(false, "Entity " ~ info.name ~ " with id " ~ to!string(id) ~ " not found");
445         }
446     }
447 
448     private void saveRelations(const EntityInfo ei, Object obj) {
449         foreach(p; ei) {
450             if (p.manyToMany) {
451                 saveRelations(p, obj);
452             }
453         }
454     }
455 
456     private void saveRelations(const PropertyInfo p, Object obj) {
457         Object[] relations = p.getCollectionFunc(obj);
458         Variant thisId = p.entity.getKey(obj);
459         if (relations !is null && relations.length > 0) {
460             string sql = p.joinTable.getInsertSQL(dialect);
461             string list;
462             foreach(r; relations) {
463                 Variant otherId = p.referencedEntity.getKey(r);
464                 if (list.length != 0)
465                     list ~= ", ";
466                 list ~= createKeyPairSQL(thisId, otherId);
467             }
468             sql ~= list;
469             Statement stmt = conn.createStatement();
470             scope(exit) stmt.close();
471             //trace("sql: " ~ sql);
472             stmt.executeUpdate(sql);
473         }
474     }
475     
476     private void updateRelations(const EntityInfo ei, Object obj) {
477         foreach(p; ei) {
478             if (p.manyToMany) {
479                 updateRelations(p, obj);
480             }
481         }
482     }
483 
484     private void deleteRelations(const EntityInfo ei, Object obj) {
485         foreach(p; ei) {
486             if (p.manyToMany) {
487                 deleteRelations(p, obj);
488             }
489         }
490     }
491     
492     private Variant[] readRelationIds(const PropertyInfo p, Variant thisId) {
493         Variant[] res;
494         string q = p.joinTable.getOtherKeySelectSQL(dialect, createKeySQL(thisId));
495         Statement stmt = conn.createStatement();
496         scope(exit) stmt.close();
497         ResultSet rs = stmt.executeQuery(q);
498         scope(exit) rs.close();
499         while (rs.next()) {
500             res ~= rs.getVariant(1);
501         }
502         return res;
503     }
504 
505     private void updateRelations(const PropertyInfo p, Object obj) {
506         Variant thisId = p.entity.getKey(obj);
507         Variant[] oldRelIds = readRelationIds(p, thisId);
508         Variant[] newRelIds = p.getCollectionIds(obj);
509         bool[string] oldmap;
510         foreach(v; oldRelIds)
511             oldmap[createKeySQL(v)] = true;
512         bool[string] newmap;
513         foreach(v; newRelIds)
514             newmap[createKeySQL(v)] = true;
515         string[] keysToDelete;
516         string[] keysToAdd;
517         foreach(v; newmap.keys)
518             if ((v in oldmap) is null)
519                 keysToAdd ~= v;
520         foreach(v; oldmap.keys)
521             if ((v in newmap) is null)
522                 keysToDelete ~= v;
523         if (keysToAdd.length > 0) {
524             Statement stmt = conn.createStatement();
525             scope(exit) stmt.close();
526             stmt.executeUpdate(p.joinTable.getInsertSQL(dialect, createKeySQL(thisId), keysToAdd));
527         }
528         if (keysToDelete.length > 0) {
529             Statement stmt = conn.createStatement();
530             scope(exit) stmt.close();
531             stmt.executeUpdate(p.joinTable.getDeleteSQL(dialect, createKeySQL(thisId), keysToDelete));
532         }
533     }
534     
535     private void deleteRelations(const PropertyInfo p, Object obj) {
536         Variant thisId = p.entity.getKey(obj);
537         Variant[] oldRelIds = readRelationIds(p, thisId);
538         string[] ids;
539         foreach(v; oldRelIds)
540             ids ~= createKeySQL(v);
541         if (ids.length > 0) {
542             Statement stmt = conn.createStatement();
543             scope(exit) stmt.close();
544             stmt.executeUpdate(p.joinTable.getDeleteSQL(dialect, createKeySQL(thisId), ids));
545         }
546     }
547     
548 
549     /// Persist the given transient instance, first assigning a generated identifier if not assigned; returns generated value
550     override Variant save(Object obj) {
551         auto info = metaData.findEntityForObject(obj);
552         if (!info.isKeySet(obj)) {
553             if (info.getKeyProperty().generated) {
554                 // autogenerated on DB side
555                 string query = dialect.appendInsertToFetchGeneratedKey(metaData.generateInsertNoKeyForEntity(dialect, info), info);
556 				PreparedStatement stmt = conn.prepareStatement(query);
557 				scope(exit) stmt.close();
558 				metaData.writeAllColumns(obj, stmt, 1, true);
559 				Variant generatedKey;
560 				stmt.executeUpdate(generatedKey);
561 			    info.setKey(obj, generatedKey);
562             } else if (info.getKeyProperty().generatorFunc !is null) {
563                 // has generator function
564                 Variant generatedKey = info.getKeyProperty().generatorFunc(conn, info.getKeyProperty());
565                 info.setKey(obj, generatedKey);
566                 string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
567                 PreparedStatement stmt = conn.prepareStatement(query);
568                 scope(exit) stmt.close();
569                 metaData.writeAllColumns(obj, stmt, 1);
570                 stmt.executeUpdate();
571             } else {
572                 throw new PropertyValueException("Key is not set and no generator is specified");
573             }
574         } else {
575 			string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
576 			PreparedStatement stmt = conn.prepareStatement(query);
577 			scope(exit) stmt.close();
578 			metaData.writeAllColumns(obj, stmt, 1);
579 			stmt.executeUpdate();
580         }
581         Variant key = info.getKey(obj);
582         putToCache(info.name, key, obj);
583         saveRelations(info, obj);
584         return key;
585     }
586 
587 	/// Persist the given transient instance.
588 	override void persist(Object obj) {
589         auto info = metaData.findEntityForObject(obj);
590         enforceHelper!TransientObjectException(info.isKeySet(obj), "Cannot persist entity w/o key assigned");
591 		string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
592 		PreparedStatement stmt = conn.prepareStatement(query);
593 		scope(exit) stmt.close();
594 		metaData.writeAllColumns(obj, stmt, 1);
595 		stmt.executeUpdate();
596         Variant key = info.getKey(obj);
597         putToCache(info.name, key, obj);
598         saveRelations(info, obj);
599     }
600 
601     override void update(Object obj) {
602         auto info = metaData.findEntityForObject(obj);
603         enforceHelper!TransientObjectException(info.isKeySet(obj), "Cannot persist entity w/o key assigned");
604 		string query = metaData.generateUpdateForEntity(dialect, info);
605 		//trace("Query: " ~ query);
606         {
607     		PreparedStatement stmt = conn.prepareStatement(query);
608     		scope(exit) stmt.close();
609     		int columnCount = metaData.writeAllColumns(obj, stmt, 1, true);
610     		info.keyProperty.writeFunc(obj, stmt, columnCount + 1);
611     		stmt.executeUpdate();
612         }
613         updateRelations(info, obj);
614 	}
615 
616     // renamed from Session.delete since delete is D keyword
617     override void remove(Object obj) {
618         auto info = metaData.findEntityForObject(obj);
619         deleteRelations(info, obj);
620         string query = "DELETE FROM " ~ dialect.quoteIfNeeded(info.tableName) ~ " WHERE " ~ dialect.quoteIfNeeded(info.getKeyProperty().columnName) ~ "=?";
621 		PreparedStatement stmt = conn.prepareStatement(query);
622 		info.getKeyProperty().writeFunc(obj, stmt, 1);
623 		stmt.executeUpdate();
624 	}
625 
626 	/// Create a new instance of Query for the given HQL query string
627 	override Query createQuery(string queryString) {
628 		return new QueryImpl(this, queryString);
629 	}
630 }
631 
632 /// Implementation of HibernateD SessionFactory
633 class SessionFactoryImpl : SessionFactory {
634 //    Configuration cfg;
635 //    Mapping mapping;
636 //    Settings settings;
637 //    EventListeners listeners;
638 //    SessionFactoryObserver observer;
639     private bool closed;
640     private EntityMetaData metaData;
641     Dialect dialect;
642     DataSource connectionPool;
643 
644     SessionImpl[] activeSessions;
645 
646 
647     DBInfo _dbInfo;
648     override public DBInfo getDBMetaData() {
649         if (_dbInfo is null)
650             _dbInfo = new DBInfo(dialect, metaData);
651         return _dbInfo;
652     }
653     
654 
655     void sessionClosed(SessionImpl session) {
656         foreach(i, item; activeSessions) {
657             if (item == session) {
658                 remove(activeSessions, i);
659             }
660         }
661     }
662 
663     this(EntityMetaData metaData, Dialect dialect, DataSource connectionPool) {
664         trace("Creating session factory");
665         this.metaData = metaData;
666         this.dialect = dialect;
667         this.connectionPool = connectionPool;
668     }
669 
670 //    this(Configuration cfg, Mapping mapping, Settings settings, EventListeners listeners, SessionFactoryObserver observer) {
671 //        this.cfg = cfg;
672 //        this.mapping = mapping;
673 //        this.settings = settings;
674 //        this.listeners = listeners;
675 //        this.observer = observer;
676 //        if (observer !is null)
677 //            observer.sessionFactoryCreated(this);
678 //    }
679     private void checkClosed() {
680         enforceHelper!SessionException(!closed, "Session factory is closed");
681     }
682 
683 	override void close() {
684         trace("Closing session factory");
685         checkClosed();
686         closed = true;
687 //        if (observer !is null)
688 //            observer.sessionFactoryClosed(this);
689         // TODO:
690     }
691 
692 	bool isClosed() {
693         return closed;
694     }
695 
696 	Session openSession() {
697         checkClosed();
698         SessionImpl session = new SessionImpl(this, metaData, dialect, connectionPool);
699         activeSessions ~= session;
700         return session;
701     }
702 }
703 
704 struct ObjectList {
705     Object[] list;
706     void add(Object obj) {
707         foreach(v; list) {
708             if (v == obj) {
709                 return; // avoid duplicates
710             }
711         }
712         list ~= obj;
713     }
714     @property int length() { return cast(int)list.length; }
715     ref Object opIndex(size_t index) {
716         return list[index];
717     }
718 }
719 
720 Variant normalize(Variant v) {
721     // variants of different type are not equal, normalize to make sure that keys Variant(1) and Variant(1L) are the same
722     if (v.convertsTo!long)
723         return Variant(v.get!long);
724     else if (v.convertsTo!ulong)
725         return Variant(v.get!ulong);
726     return Variant(v.toString());
727 }
728 
729 /// task to load reference entity
730 class PropertyLoadItem {
731     const PropertyInfo property;
732     private ObjectList[Variant] _map; // ID to object list
733     this(const PropertyInfo property) {
734         this.property = property;
735     }
736     @property ref ObjectList[Variant] map() { return _map; }
737     @property Variant[] keys() { return _map.keys; }
738     @property int length() { return cast(int)_map.length; }
739     ObjectList * opIndex(Variant key) {
740         Variant id = normalize(key);
741         if ((id in _map) is null) {
742             _map[id] = ObjectList();
743         }
744         //assert(length > 0);
745         return &_map[id];
746     }
747     void add(ref Variant id, Object obj) {
748         auto item = opIndex(id);
749         item.add(obj);
750         //assert(item.length == opIndex(id).length);
751     }
752     string createCommaSeparatedKeyList() {
753         assert(map.length > 0);
754         return .createCommaSeparatedKeyList(map.keys);
755     }
756 }
757 
758 string createKeySQL(Variant id) {
759     if (id.convertsTo!long || id.convertsTo!ulong) {
760         return id.toString();
761     } else {
762         return "'" ~ id.toString() ~ "'";
763     }
764 }
765 
766 string createKeyPairSQL(Variant id1, Variant id2) {
767     return "(" ~ createKeySQL(id1) ~ ", " ~ createKeySQL(id2) ~ ")";
768 }
769 
770 string createCommaSeparatedKeyList(Variant[] list) {
771     assert(list.length > 0);
772     string res;
773     foreach(v; list) {
774         if (res.length > 0)
775             res ~= ", ";
776         res ~= createKeySQL(v);
777     }
778     return res;
779 }
780 
781 /// task to load reference entity
782 class EntityCollections {
783     private ObjectList[Variant] _map; // ID to object list
784     @property ref ObjectList[Variant] map() { return _map; }
785     @property Variant[] keys() { return _map.keys; }
786     @property int length() { return cast(int)_map.length; }
787     ref Object[] opIndex(Variant key) {
788         //trace("searching for key " ~ key.toString);
789         Variant id = normalize(key);
790         if ((id in _map) is null) {
791             //trace("creating new item");
792             _map[id] = ObjectList();
793         }
794         //assert(length > 0);
795         //trace("returning item");
796         return _map[id].list;
797     }
798     void add(ref Variant id, Object obj) {
799         auto item = opIndex(id);
800         //trace("item count = " ~ to!string(item.length));
801         item ~= obj;
802     }
803 }
804 
805 class PropertyLoadMap {
806     private PropertyLoadItem[const PropertyInfo] _map;
807     PropertyLoadItem opIndex(const PropertyInfo prop) {
808         if ((prop in _map) is null) {
809             //trace("creating new PropertyLoadItem for " ~ prop.propertyName);
810             _map[prop] = new PropertyLoadItem(prop);
811         }
812         assert(_map.length > 0);
813         return _map[prop];
814     }
815 
816     this() {}
817 
818     this(PropertyLoadMap plm) {
819         foreach(k; plm.keys) {
820             auto pli = plm[k];
821             foreach(plik; pli.map.keys) {
822                 foreach(obj; pli.map[plik].list.dup) {
823                     add(k, plik, obj);
824                 }
825             }
826         }
827     }
828 
829     PropertyLoadItem remove(const PropertyInfo pi) {
830         PropertyLoadItem item = _map[pi];
831         _map.remove(pi);
832         return item;
833     }
834     @property ref PropertyLoadItem[const PropertyInfo] map() { return _map; }
835     @property int length() { return cast(int)_map.length; }
836     @property const (PropertyInfo)[] keys() { return _map.keys; }
837     void add(const PropertyInfo property, Variant id, Object obj) {
838         auto item = opIndex(property);
839         item.add(id, obj);
840         //assert(item.length > 0);
841     }
842 }
843 
844 /// Implementation of HibernateD Query
845 class QueryImpl : Query
846 {
847 	SessionImpl sess;
848 	ParsedQuery query;
849 	ParameterValues params;
850 	this(SessionImpl sess, string queryString) {
851 		this.sess = sess;
852         //trace("QueryImpl(): HQL: " ~ queryString);
853         QueryParser parser = new QueryParser(sess.metaData, queryString);
854         //trace("parsing");
855 		this.query = parser.makeSQL(sess.dialect);
856         //trace("SQL: " ~ this.query.sql);
857         params = query.createParams();
858         //trace("exiting QueryImpl()");
859     }
860 
861 	///Get the query string.
862 	override string getQueryString() {
863 		return query.hql;
864 	}
865 
866 	/// Convenience method to return a single instance that matches the query, or null if the query returns no results.
867 	override Object uniqueObject() {
868         return uniqueResult(cast(Object)null);
869 	}
870 
871     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
872     override Object uniqueObject(Object obj) {
873         Object[] rows = listObjects(obj);
874         if (rows == null)
875             return null;
876         enforceHelper!NonUniqueResultException(rows.length == 1, "Query returned more than one object: " ~ getQueryString());
877         return rows[0];
878     }
879 
880 	/// Convenience method to return a single instance that matches the query, or null if the query returns no results.
881 	override Variant[] uniqueRow() {
882 		Variant[][] rows = listRows();
883 		if (rows == null)
884 			return null;
885         enforceHelper!NonUniqueResultException(rows.length == 1, "Query returned more than one row: " ~ getQueryString());
886 		return rows[0];
887 	}
888 
889     private FromClauseItem findRelation(FromClauseItem from, const PropertyInfo prop) {
890         for (int i=0; i<query.from.length; i++) {
891             FromClauseItem f = query.from[i];
892             if (f.base == from && f.baseProperty == prop)
893                 return f;
894             if (f.entity == prop.referencedEntity && from.base == f)
895                 return f;
896         }
897         return null;
898     }
899 
900     private Object readRelations(Object objectContainer, DataSetReader r, PropertyLoadMap loadMap) {
901         Object[] relations = new Object[query.select.length];
902         //trace("select clause len = " ~ to!string(query.select.length));
903         // read all related objects from DB row
904         for (int i = 0; i < query.select.length; i++) {
905             FromClauseItem from = query.select[i].from;
906             //trace("reading " ~ from.entityName);
907             Object row;
908             if (!from.entity.isKeyNull(r, from.startColumn)) {
909                 //trace("key is not null");
910                 Variant key = from.entity.getKey(r, from.startColumn);
911                 //trace("key is " ~ key.toString);
912                 row = sess.peekFromCache(from.entity.name, key);
913                 if (row is null) {
914                     //trace("row not found in cache");
915                     row = (objectContainer !is null && i == 0) ? objectContainer : from.entity.createEntity();
916                     //trace("reading all columns");
917                     sess.metaData.readAllColumns(row, r, from.startColumn);
918                     sess.putToCache(from.entity.name, key, row);
919                 } else if (objectContainer !is null) {
920                     //trace("copying all properties to existing container");
921                     from.entity.copyAllProperties(objectContainer, row);
922                 }
923             }
924             relations[i] = row;
925         }
926         //trace("fill relations...");
927         // fill relations
928         for (int i = 0; i < query.select.length; i++) {
929             if (relations[i] is null)
930                 continue;
931             FromClauseItem from = query.select[i].from;
932             auto ei = from.entity;
933             for (int j=0; j<ei.length; j++) {
934                 auto pi = ei[j];
935                 if (pi.oneToOne || pi.manyToOne) {
936                     trace("updating relations for " ~ from.pathString ~ "." ~ pi.propertyName);
937                     FromClauseItem rfrom = findRelation(from, pi);
938                     if (rfrom !is null && rfrom.selectIndex >= 0) {
939                         Object rel = relations[rfrom.selectIndex];
940                         pi.setObjectFunc(relations[i], rel);
941                     } else {
942                         if (pi.columnName !is null) {
943                             trace("relation " ~ pi.propertyName ~ " has column name");
944                             if (r.isNull(from.startColumn + pi.columnOffset)) {
945                                 // FK is null, set NULL to field
946                                 pi.setObjectFunc(relations[i], null);
947                                 trace("relation " ~ pi.propertyName ~ " has null FK");
948                             } else {
949                                 Variant id = r.getVariant(from.startColumn + pi.columnOffset);
950                                 Object existing = sess.peekFromCache(pi.referencedEntity.name, id);
951                                 if (existing !is null) {
952                                     trace("existing relation found in cache");
953                                     pi.setObjectFunc(relations[i], existing);
954                                 } else {
955                                     // FK is not null
956                                     if (pi.lazyLoad) {
957                                         // lazy load
958                                         trace("scheduling lazy load for " ~ from.pathString ~ "." ~ pi.propertyName ~ " with FK " ~ id.toString);
959                                         LazyObjectLoader loader = new LazyObjectLoader(sess.accessor, pi, id);
960                                         pi.setObjectDelegateFunc(relations[i], &loader.load);
961                                     } else {
962                                         // delayed load
963                                         trace("relation " ~ pi.propertyName ~ " with FK " ~ id.toString() ~ " will be loaded later");
964                                         loadMap.add(pi, id, relations[i]); // to load later
965                                     }
966                                 }
967                             }
968                         } else {
969                             // TODO:
970                             assert(false, "relation " ~ pi.propertyName ~ " has no column name. To be implemented.");
971                         }
972                     }
973                 } else if (pi.oneToMany || pi.manyToMany) {
974                     Variant id = ei.getKey(relations[i]);
975                     if (pi.lazyLoad) {
976                         // lazy load
977                         trace("creating lazy loader for " ~ from.pathString ~ "." ~ pi.propertyName ~ " by FK " ~ id.toString);
978                         LazyCollectionLoader loader = new LazyCollectionLoader(sess.accessor, pi, id);
979                         pi.setCollectionDelegateFunc(relations[i], &loader.load);
980                     } else {
981                         // delayed load
982                         trace("Relation " ~ from.pathString ~ "." ~ pi.propertyName ~ " will be loaded later by FK " ~ id.toString);
983                         loadMap.add(pi, id, relations[i]); // to load later
984                     }
985                 }
986             }
987         }
988         return relations[0];
989     }
990 
991 	/// Return the query results as a List of entity objects
992 	override Object[] listObjects() {
993         return listObjects(null);
994 	}
995 
996     private void delayedLoadRelations(PropertyLoadMap loadMap) {
997         loadMap = new PropertyLoadMap(loadMap);
998 
999         auto types = loadMap.keys;
1000         trace("delayedLoadRelations " ~ to!string(loadMap.length));
1001 
1002         foreach(pi; types) {
1003             trace("delayedLoadRelations " ~ pi.entity.name ~ "." ~ pi.propertyName);
1004             assert(pi.referencedEntity !is null);
1005             auto map = loadMap.remove(pi);
1006             if (map.length == 0)
1007                 continue;
1008             //trace("delayedLoadRelations " ~ pi.entity.name ~ "." ~ pi.propertyName);
1009             string keys = map.createCommaSeparatedKeyList();
1010             if (pi.oneToOne || pi.manyToOne) {
1011                 if (pi.columnName !is null) {
1012                     Variant[] unknownKeys;
1013                     Object[] list = sess.lookupCache(pi.referencedEntity.name, map.keys, unknownKeys);
1014                     if (unknownKeys.length > 0) {
1015                         string hql = "FROM " ~ pi.referencedEntity.name ~ " WHERE " ~ pi.referencedEntity.keyProperty.propertyName ~ " IN (" ~ createCommaSeparatedKeyList(unknownKeys) ~ ")";
1016                         trace("delayedLoadRelations: loading " ~ pi.propertyName ~ " HQL: " ~ hql);
1017                         QueryImpl q = cast(QueryImpl)sess.createQuery(hql);
1018                         Object[] fromDB = q.listObjects(null, loadMap);
1019                         list ~= fromDB;
1020                         trace("delayedLoadRelations: objects loaded " ~ to!string(fromDB.length));
1021                     } else {
1022                         trace("all objects found in cache");
1023                     }
1024                     trace("delayedLoadRelations: updating");
1025                     foreach(rel; list) {
1026                         trace("delayedLoadRelations: reading key from " ~ pi.referencedEntity.name);
1027                         Variant key = pi.referencedEntity.getKey(rel);
1028                         //trace("map length before: " ~ to!string(map.length));
1029                         auto objectsToUpdate = map[key].list;
1030                         //trace("map length after: " ~ to!string(map.length));
1031                         //trace("updating relations with key " ~ key.toString() ~ " (" ~ to!string(objectsToUpdate.length) ~ ")");
1032                         foreach(obj; objectsToUpdate) {
1033                             pi.setObjectFunc(obj, rel);
1034                         }
1035                     }
1036                 } else {
1037                     assert(false, "Delayed loader for non-join column is not yet implemented for OneToOne and ManyToOne");
1038                 }
1039             } else if (pi.oneToMany || pi.manyToMany) {
1040                 string hql = "FROM " ~ pi.referencedEntity.name ~ " WHERE " ~ pi.referencedPropertyName ~ "." ~ pi.referencedEntity.keyProperty.propertyName ~ " IN (" ~ keys ~ ")";
1041                 trace("delayedLoadRelations: loading " ~ pi.propertyName ~ " HQL: " ~ hql);
1042                 QueryImpl q = cast(QueryImpl)sess.createQuery(hql);
1043                 assert(q !is null);
1044                 Object[] list = q.listObjects(null, loadMap);
1045                 trace("delayedLoadRelations oneToMany/manyToMany: objects loaded " ~ to!string(list.length));
1046                 EntityCollections collections = new EntityCollections();
1047                 // group by referenced PK
1048                 foreach(rel; list) {
1049                     trace("delayedLoadRelations oneToMany/manyToMany: reading reference from " ~ pi.referencedEntity.name ~ "." ~ pi.referencedProperty.propertyName ~ " joinColumn=" ~ pi.referencedProperty.columnName);
1050                     assert(pi.referencedProperty.manyToOne, "property referenced from OneToMany should be ManyToOne");
1051                     assert(pi.referencedProperty.getObjectFunc !is null);
1052                     assert(rel !is null);
1053                     //trace("delayedLoadRelations oneToMany: reading object " ~ rel.classinfo.toString);
1054                     Object obj = pi.referencedProperty.getObjectFunc(rel);
1055                     //trace("delayedLoadRelations oneToMany: object is read");
1056                     if (obj !is null) {
1057                         //trace("delayedLoadRelations oneToMany: object is not null");
1058                         //trace("pi.entity.name=" ~ pi.entity.name ~ ", obj is " ~ obj.classinfo.toString);
1059                         //trace("obj = " ~ obj.toString);
1060                         //trace("pi.entity.keyProperty=" ~ pi.entity.keyProperty.propertyName);
1061                         //assert(pi.entity.keyProperty.getFunc !is null);
1062                         //Variant k = pi.entity.keyProperty.getFunc(obj);
1063                         //trace("key=" ~ k.toString);
1064                         Variant key = pi.entity.getKey(obj);
1065                         collections[key] ~= rel;
1066                         //collections.add(k, rel);
1067                     }
1068                 }
1069                 // update objects
1070                 foreach(key; collections.keys) {
1071                     auto objectsToUpdate = map[key].list;
1072                     foreach(obj; objectsToUpdate) {
1073                         pi.setCollectionFunc(obj, collections[key]);
1074                     }
1075                 }
1076             }
1077         }
1078     }
1079 
1080     /// Return the query results as a List of entity objects
1081     Object[] listObjects(Object placeFirstObjectHere) {
1082         PropertyLoadMap loadMap = new PropertyLoadMap();
1083         return listObjects(placeFirstObjectHere, loadMap);
1084     }
1085 
1086     /// Return the query results as a List of entity objects
1087     Object[] listObjects(Object placeFirstObjectHere, PropertyLoadMap loadMap) {
1088         trace("Entering listObjects " ~ query.hql);
1089         auto ei = query.entity;
1090         enforceHelper!SessionException(ei !is null, "No entity expected in result of query " ~ getQueryString());
1091         params.checkAllParametersSet();
1092         sess.checkClosed();
1093         
1094         Object[] res;
1095 
1096 
1097         //trace("SQL: " ~ query.sql);
1098         PreparedStatement stmt = sess.conn.prepareStatement(query.sql);
1099         scope(exit) stmt.close();
1100         params.applyParams(stmt);
1101         ResultSet rs = stmt.executeQuery();
1102         assert(query.select !is null && query.select.length > 0);
1103         int startColumn = query.select[0].from.startColumn;
1104         {
1105             scope(exit) rs.close();
1106             while(rs.next()) {
1107                 //trace("read relations...");
1108                 Object row = readRelations(res.length > 0 ? null : placeFirstObjectHere, rs, loadMap);
1109                 if (row !is null)
1110                     res ~= row;
1111             }
1112         }
1113         if (loadMap.length > 0) {
1114             trace("relation properties scheduled for load: loadMap.length == " ~ to!string(loadMap.length));
1115             delayedLoadRelations(loadMap);
1116         }
1117         trace("Exiting listObjects " ~ query.hql);
1118         return res.length > 0 ? res : null;
1119     }
1120     
1121     /// Return the query results as a List which each row as Variant array
1122 	override Variant[][] listRows() {
1123 		params.checkAllParametersSet();
1124 		sess.checkClosed();
1125 		
1126 		Variant[][] res;
1127 		
1128 		//trace("SQL: " ~ query.sql);
1129 		PreparedStatement stmt = sess.conn.prepareStatement(query.sql);
1130 		scope(exit) stmt.close();
1131 		params.applyParams(stmt);
1132 		ResultSet rs = stmt.executeQuery();
1133 		scope(exit) rs.close();
1134 		while(rs.next()) {
1135 			Variant[] row = new Variant[query.colCount];
1136 			for (int i = 1; i<=query.colCount; i++)
1137 				row[i - 1] = rs.getVariant(i);
1138 			res ~= row;
1139 		}
1140 		return res.length > 0 ? res : null;
1141 	}
1142 	
1143 	/// Bind a value to a named query parameter.
1144 	override protected Query setParameterVariant(string name, Variant val) {
1145 		params.setParameter(name, val);
1146 		return this;
1147 	}
1148 }
1149 
1150 class LazyObjectLoader {
1151     const PropertyInfo pi;
1152     Variant id;
1153     SessionAccessor sess;
1154     this(SessionAccessor sess, const PropertyInfo pi, Variant id) {
1155         trace("Created lazy loader for " ~ pi.referencedEntityName ~ " with id " ~ id.toString);
1156         this.pi = pi;
1157         this.id = id;
1158         this.sess = sess;
1159     }
1160     Object load() {
1161         trace("LazyObjectLoader.load()");
1162         trace("lazy loading of " ~ pi.referencedEntityName ~ " with id " ~ id.toString);
1163         return sess.get().loadObject(pi.referencedEntityName, id);
1164     }
1165 }
1166 
1167 class LazyCollectionLoader {
1168     const PropertyInfo pi;
1169     Variant fk;
1170     SessionAccessor sess;
1171     this(SessionAccessor sess, const PropertyInfo pi, Variant fk) {
1172         assert(!pi.oneToMany || (pi.referencedEntity !is null && pi.referencedProperty !is null), "LazyCollectionLoader: No referenced property specified for OneToMany foreign key column");
1173         trace("Created lazy loader for collection for references " ~ pi.entity.name ~ "." ~ pi.propertyName ~ " by id " ~ fk.toString);
1174         this.pi = pi;
1175         this.fk = fk;
1176         this.sess = sess;
1177     }
1178     Object[] load() {
1179         trace("LazyObjectLoader.load()");
1180         trace("lazy loading of references " ~ pi.entity.name ~ "." ~ pi.propertyName ~ " by id " ~ fk.toString);
1181         Object[] res = sess.get().loadReferencedObjects(pi.entity, pi.propertyName, fk);
1182         return res;
1183     }
1184 }
1185