1 /**
2  * DStruct - 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 dstruct/session.d.
8  *
9  * This module contains implementation of DStruct 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 dstruct.session;
16 
17 import std.algorithm;
18 import std.conv;
19 import std.stdio;
20 import std.exception;
21 import std.variant;
22 
23 import dstruct.ddbc.core;
24 import dstruct.ddbc.common;
25 
26 import dstruct.type;
27 import dstruct.dialect;
28 import dstruct.core;
29 import dstruct.metadata;
30 import dstruct.query;
31 
32 // For backwards compatibily
33 // 'enforceEx' will be removed with 2.089
34 static if (__VERSION__ < 2080)
35 {
36     alias enforceHelper = enforceEx;
37 }
38 else
39 {
40     alias enforceHelper = enforce;
41 }
42 
43 const TRACE_REFS = false;
44 
45 /// Factory to create DStruct Sessions - similar to org.hibernate.SessionFactory
46 interface SessionFactory
47 {
48     /// close all active sessions
49     void close();
50     /// check if session factory is closed
51     bool isClosed();
52     /// creates new session
53     Session openSession();
54     /// retrieve information about tables and indexes for schema
55     DBInfo getDBMetaData();
56 }
57 
58 /// Session - main interface to load and persist entities -- similar to org.hibernate.Session
59 abstract class Session
60 {
61     /// returns metadata
62     EntityMetaData getMetaData();
63 
64     /// not supported in current implementation
65     Transaction beginTransaction();
66     /// not supported in current implementation
67     void cancelQuery();
68     /// not supported in current implementation
69     void clear();
70 
71     /// closes session
72     Connection close();
73 
74     ///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?
75     bool isDirty();
76     /// Check if the session is still open.
77     bool isOpen();
78     /// Check if the session is currently connected.
79     bool isConnected();
80     /// Check if this instance is associated with this Session.
81     bool contains(Object object);
82     /// Retrieve session factory used to create this session
83     SessionFactory getSessionFactory();
84     /// Lookup metadata to find entity name for object.
85     string getEntityName(Object object);
86     /// Lookup metadata to find entity name for class type info.
87     string getEntityName(TypeInfo_Class type);
88 
89     /// Return the persistent instance of the given named entity with the given identifier, or null if there is no such persistent instance.
90     T get(T : Object, ID)(ID id)
91     {
92         Variant v = id;
93         return cast(T) getObject(getEntityName(T.classinfo), v);
94     }
95 
96     /// Read the persistent state associated with the given identifier into the given transient instance.
97     T load(T : Object, ID)(ID id)
98     {
99         Variant v = id;
100         return cast(T) loadObject(getEntityName(T.classinfo), v);
101     }
102 
103     /// Read the persistent state associated with the given identifier into the given transient instance.
104     void load(T : Object, ID)(T obj, ID id)
105     {
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 
143 /// Interface for usage of HQL queries.
144 abstract class Query
145 {
146     ///Get the query string.
147     string getQueryString();
148     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
149     Object uniqueObject();
150     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
151     Object uniqueObject(Object obj);
152     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
153     T uniqueResult(T : Object)()
154     {
155         return cast(T) uniqueObject();
156     }
157     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
158     T uniqueResult(T : Object)(T obj)
159     {
160         return cast(T) uniqueObject(obj);
161     }
162 
163     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
164     Variant[] uniqueRow();
165     /// Return the query results as a List of entity objects
166     Object[] listObjects();
167     /// Return the query results as a List of entity objects
168     T[] list(T : Object)()
169     {
170         return cast(T[]) listObjects();
171     }
172     /// Return the query results as a List which each row as Variant array
173     Variant[][] listRows();
174 
175     /// Bind a value to a named query parameter (all :parameters used in query should be bound before executing query).
176     protected Query setParameterVariant(string name, Variant val);
177 
178     /// Bind a value to a named query parameter (all :parameters used in query should be bound before executing query).
179     Query setParameter(T)(string name, T val)
180     {
181         static if (is(T == Variant))
182         {
183             return setParameterVariant(name, val);
184         }
185         else
186         {
187             return setParameterVariant(name, Variant(val));
188         }
189     }
190 }
191 
192 /// Allows reaction to basic SessionFactory occurrences
193 interface SessionFactoryObserver
194 {
195     ///Callback to indicate that the given factory has been closed.
196     void sessionFactoryClosed(SessionFactory factory);
197     ///Callback to indicate that the given factory has been created and is now ready for use.
198     void sessionFactoryCreated(SessionFactory factory);
199 }
200 
201 interface EventListeners
202 {
203     // TODO:
204 }
205 
206 interface ConnectionProvider
207 {
208 }
209 
210 interface Settings
211 {
212     Dialect getDialect();
213     ConnectionProvider getConnectionProvider();
214     bool isAutoCreateSchema();
215 }
216 
217 interface Mapping
218 {
219     string getIdentifierPropertyName(string className);
220     Type getIdentifierType(string className);
221     Type getReferencedPropertyType(string className, string propertyName);
222 }
223 
224 class Configuration
225 {
226     bool dummy;
227 }
228 
229 class EntityCache
230 {
231     const string name;
232     const EntityInfo entity;
233     Object[Variant] items;
234     this(const EntityInfo entity)
235     {
236         this.entity = entity;
237         this.name = entity.name;
238     }
239 
240     bool hasKey(Variant key)
241     {
242         return ((key in items) !is null);
243     }
244 
245     Object peek(Variant key)
246     {
247         if ((key in items) is null)
248             return null;
249         return items[key];
250     }
251 
252     Object get(Variant key)
253     {
254         enforceHelper!CacheException((key in items) !is null,
255                 "entity " ~ name ~ " with key " ~ key.toString() ~ " not found in cache");
256         return items[key];
257     }
258 
259     void put(Variant key, Object obj)
260     {
261         items[key] = obj;
262     }
263 
264     Object[] lookup(Variant[] keys, out Variant[] unknownKeys)
265     {
266         Variant[] unknown;
267         Object[] res;
268         foreach (key; keys)
269         {
270             Object obj = peek(normalize(key));
271             if (obj !is null)
272             {
273                 res ~= obj;
274             }
275             else
276             {
277                 unknown ~= normalize(key);
278             }
279         }
280         unknownKeys = unknown;
281         return res;
282     }
283 }
284 
285 /// helper class to disconnect Lazy loaders from closed session.
286 class SessionAccessor
287 {
288     private SessionImpl _session;
289 
290     this(SessionImpl session)
291     {
292         _session = session;
293     }
294     /// returns session, with session state check - throws LazyInitializationException if attempting to get unfetched lazy data while session is closed
295     SessionImpl get()
296     {
297         enforceHelper!LazyInitializationException(_session !is null,
298                 "Cannot read from closed session");
299         return _session;
300     }
301     /// nulls session reference
302     void onSessionClosed()
303     {
304         _session = null;
305     }
306 }
307 
308 /// Implementation of DStruct session
309 class SessionImpl : Session
310 {
311 
312     private bool closed;
313     SessionFactoryImpl sessionFactory;
314     private EntityMetaData metaData;
315     Dialect dialect;
316     DataSource connectionPool;
317     Connection conn;
318 
319     EntityCache[string] cache;
320 
321     private SessionAccessor _accessor;
322     @property SessionAccessor accessor()
323     {
324         return _accessor;
325     }
326 
327     package EntityCache getCache(string entityName)
328     {
329         EntityCache res;
330         if ((entityName in cache) is null)
331         {
332             res = new EntityCache(metaData[entityName]);
333             cache[entityName] = res;
334         }
335         else
336         {
337             res = cache[entityName];
338         }
339         return res;
340     }
341 
342     package bool keyInCache(string entityName, Variant key)
343     {
344         return getCache(entityName).hasKey(normalize(key));
345     }
346 
347     package Object peekFromCache(string entityName, Variant key)
348     {
349         return getCache(entityName).peek(normalize(key));
350     }
351 
352     package Object getFromCache(string entityName, Variant key)
353     {
354         return getCache(entityName).get(normalize(key));
355     }
356 
357     package void putToCache(string entityName, Variant key, Object value)
358     {
359         return getCache(entityName).put(normalize(key), value);
360     }
361 
362     package Object[] lookupCache(string entityName, Variant[] keys, out Variant[] unknownKeys)
363     {
364         return getCache(entityName).lookup(keys, unknownKeys);
365     }
366 
367     override EntityMetaData getMetaData()
368     {
369         return metaData;
370     }
371 
372     private void checkClosed()
373     {
374         enforceHelper!SessionException(!closed, "Session is closed");
375     }
376 
377     this(SessionFactoryImpl sessionFactory, EntityMetaData metaData,
378             Dialect dialect, DataSource connectionPool)
379     {
380         //writeln("Creating session");
381         this.sessionFactory = sessionFactory;
382         this.metaData = metaData;
383         this.dialect = dialect;
384         this.connectionPool = connectionPool;
385         this.conn = connectionPool.getConnection();
386         this._accessor = new SessionAccessor(this);
387     }
388 
389     override Transaction beginTransaction()
390     {
391         throw new DStructException("Method not implemented");
392     }
393 
394     override void cancelQuery()
395     {
396         throw new DStructException("Method not implemented");
397     }
398 
399     override void clear()
400     {
401         throw new DStructException("Method not implemented");
402     }
403     ///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?
404     override bool isDirty()
405     {
406         throw new DStructException("Method not implemented");
407     }
408     /// Check if the session is still open.
409     override bool isOpen()
410     {
411         return !closed;
412     }
413     /// Check if the session is currently connected.
414     override bool isConnected()
415     {
416         return !closed;
417     }
418     /// End the session by releasing the JDBC connection and cleaning up.
419     override Connection close()
420     {
421         checkClosed();
422         _accessor.onSessionClosed();
423         closed = true;
424         sessionFactory.sessionClosed(this);
425         //writeln("closing connection");
426         assert(conn !is null);
427         conn.close();
428         return null;
429     }
430     ///Check if this instance is associated with this Session
431     override bool contains(Object object)
432     {
433         throw new DStructException("Method not implemented");
434     }
435 
436     override SessionFactory getSessionFactory()
437     {
438         checkClosed();
439         return sessionFactory;
440     }
441 
442     override string getEntityName(Object object)
443     {
444         checkClosed();
445         return metaData.findEntityForObject(object).name;
446     }
447 
448     override string getEntityName(TypeInfo_Class type)
449     {
450         checkClosed();
451         return metaData.getEntityName(type);
452     }
453 
454     override Object getObject(string entityName, Variant id)
455     {
456         auto info = metaData.findEntity(entityName);
457         return getObject(info, null, id);
458     }
459 
460     /// Read the persistent state associated with the given identifier into the given transient instance.
461     override Object loadObject(string entityName, Variant id)
462     {
463         Object obj = getObject(entityName, id);
464         enforceHelper!ObjectNotFoundException(obj !is null,
465                 "Entity " ~ entityName ~ " with id " ~ to!string(id) ~ " not found");
466         return obj;
467     }
468 
469     /// Read the persistent state associated with the given identifier into the given transient instance
470     override void loadObject(Object obj, Variant id)
471     {
472         auto info = metaData.findEntityForObject(obj);
473         Object found = getObject(info, obj, id);
474         enforceHelper!ObjectNotFoundException(found !is null,
475                 "Entity " ~ info.name ~ " with id " ~ to!string(id) ~ " not found");
476     }
477 
478     /// Read the persistent state associated with the given identifier into the given transient instance
479     Object getObject(const EntityInfo info, Object obj, Variant id)
480     {
481         string hql = "FROM " ~ info.name ~ " WHERE " ~ info.getKeyProperty().propertyName ~ "=:Id";
482         Query q = createQuery(hql).setParameter("Id", id);
483         Object res = q.uniqueResult(obj);
484         return res;
485     }
486 
487     /// Read entities referenced by property 
488     Object[] loadReferencedObjects(const EntityInfo info, string referencePropertyName, Variant fk)
489     {
490         string hql = "SELECT a2 FROM " ~ info.name ~ " AS a1 JOIN a1." ~ referencePropertyName
491             ~ " AS a2 WHERE a1." ~ info.getKeyProperty().propertyName ~ "=:Fk";
492         Query q = createQuery(hql).setParameter("Fk", fk);
493         Object[] res = q.listObjects();
494         return res;
495     }
496 
497     /// Re-read the state of the given instance from the underlying database.
498     override void refresh(Object obj)
499     {
500         auto info = metaData.findEntityForObject(obj);
501         string query = metaData.generateFindByPkForEntity(dialect, info);
502         enforceHelper!TransientObjectException(info.isKeySet(obj),
503                 "Cannot refresh entity " ~ info.name ~ ": no Id specified");
504         Variant id = info.getKey(obj);
505         //writeln("Finder query: " ~ query);
506         PreparedStatement stmt = conn.prepareStatement(query);
507         scope (exit)
508             stmt.close();
509         stmt.setVariant(1, id);
510         ResultSet rs = stmt.executeQuery();
511         //writeln("returned rows: " ~ to!string(rs.getFetchSize()));
512         scope (exit)
513             rs.close();
514         if (rs.next())
515         {
516             //writeln("reading columns");
517             metaData.readAllColumns(obj, rs, 1);
518             //writeln("value: " ~ obj.toString);
519         }
520         else
521         {
522             // not found!
523             enforceHelper!ObjectNotFoundException(false,
524                     "Entity " ~ info.name ~ " with id " ~ to!string(id) ~ " not found");
525         }
526     }
527 
528     private void saveRelations(const EntityInfo ei, Object obj)
529     {
530         foreach (p; ei)
531         {
532             if (p.manyToMany)
533             {
534                 saveRelations(p, obj);
535             }
536         }
537     }
538 
539     private void saveRelations(const PropertyInfo p, Object obj)
540     {
541         Object[] relations = p.getCollectionFunc(obj);
542         Variant thisId = p.entity.getKey(obj);
543         if (relations !is null && relations.length > 0)
544         {
545             string sql = p.joinTable.getInsertSQL(dialect);
546             string list;
547             foreach (r; relations)
548             {
549                 Variant otherId = p.referencedEntity.getKey(r);
550                 if (list.length != 0)
551                     list ~= ", ";
552                 list ~= createKeyPairSQL(thisId, otherId);
553             }
554             sql ~= list;
555             Statement stmt = conn.createStatement();
556             scope (exit)
557                 stmt.close();
558             //writeln("sql: " ~ sql);
559             stmt.executeUpdate(sql);
560         }
561     }
562 
563     private void updateRelations(const EntityInfo ei, Object obj)
564     {
565         foreach (p; ei)
566         {
567             if (p.manyToMany)
568             {
569                 updateRelations(p, obj);
570             }
571         }
572     }
573 
574     private void deleteRelations(const EntityInfo ei, Object obj)
575     {
576         foreach (p; ei)
577         {
578             if (p.manyToMany)
579             {
580                 deleteRelations(p, obj);
581             }
582         }
583     }
584 
585     private Variant[] readRelationIds(const PropertyInfo p, Variant thisId)
586     {
587         Variant[] res;
588         string q = p.joinTable.getOtherKeySelectSQL(dialect, createKeySQL(thisId));
589         Statement stmt = conn.createStatement();
590         scope (exit)
591             stmt.close();
592         ResultSet rs = stmt.executeQuery(q);
593         scope (exit)
594             rs.close();
595         while (rs.next())
596         {
597             res ~= rs.getVariant(1);
598         }
599         return res;
600     }
601 
602     private void updateRelations(const PropertyInfo p, Object obj)
603     {
604         Variant thisId = p.entity.getKey(obj);
605         Variant[] oldRelIds = readRelationIds(p, thisId);
606         Variant[] newRelIds = p.getCollectionIds(obj);
607         bool[string] oldmap;
608         foreach (v; oldRelIds)
609             oldmap[createKeySQL(v)] = true;
610         bool[string] newmap;
611         foreach (v; newRelIds)
612             newmap[createKeySQL(v)] = true;
613         string[] keysToDelete;
614         string[] keysToAdd;
615         foreach (v; newmap.keys)
616             if ((v in oldmap) is null)
617                 keysToAdd ~= v;
618         foreach (v; oldmap.keys)
619             if ((v in newmap) is null)
620                 keysToDelete ~= v;
621         if (keysToAdd.length > 0)
622         {
623             Statement stmt = conn.createStatement();
624             scope (exit)
625                 stmt.close();
626             stmt.executeUpdate(p.joinTable.getInsertSQL(dialect, createKeySQL(thisId), keysToAdd));
627         }
628         if (keysToDelete.length > 0)
629         {
630             Statement stmt = conn.createStatement();
631             scope (exit)
632                 stmt.close();
633             stmt.executeUpdate(p.joinTable.getDeleteSQL(dialect,
634                     createKeySQL(thisId), keysToDelete));
635         }
636     }
637 
638     private void deleteRelations(const PropertyInfo p, Object obj)
639     {
640         Variant thisId = p.entity.getKey(obj);
641         Variant[] oldRelIds = readRelationIds(p, thisId);
642         string[] ids;
643         foreach (v; oldRelIds)
644             ids ~= createKeySQL(v);
645         if (ids.length > 0)
646         {
647             Statement stmt = conn.createStatement();
648             scope (exit)
649                 stmt.close();
650             stmt.executeUpdate(p.joinTable.getDeleteSQL(dialect, createKeySQL(thisId), ids));
651         }
652     }
653 
654     /// Persist the given transient instance, first assigning a generated identifier if not assigned; returns generated value
655     override Variant save(Object obj)
656     {
657         auto info = metaData.findEntityForObject(obj);
658         if (!info.isKeySet(obj))
659         {
660             if (info.getKeyProperty().generated)
661             {
662                 // autogenerated on DB side
663                 string query = dialect.appendInsertToFetchGeneratedKey(
664                         metaData.generateInsertNoKeyForEntity(dialect, info), info);
665                 PreparedStatement stmt = conn.prepareStatement(query);
666                 scope (exit)
667                     stmt.close();
668                 metaData.writeAllColumns(obj, stmt, 1, true);
669                 Variant generatedKey;
670                 stmt.executeUpdate(generatedKey);
671                 info.setKey(obj, generatedKey);
672             }
673             else if (info.getKeyProperty().generatorFunc !is null)
674             {
675                 // has generator function
676                 Variant generatedKey = info.getKeyProperty()
677                     .generatorFunc(conn, info.getKeyProperty());
678                 info.setKey(obj, generatedKey);
679                 string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
680                 PreparedStatement stmt = conn.prepareStatement(query);
681                 scope (exit)
682                     stmt.close();
683                 metaData.writeAllColumns(obj, stmt, 1);
684                 stmt.executeUpdate();
685             }
686             else
687             {
688                 throw new PropertyValueException("Key is not set and no generator is specified");
689             }
690         }
691         else
692         {
693             string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
694             PreparedStatement stmt = conn.prepareStatement(query);
695             scope (exit)
696                 stmt.close();
697             metaData.writeAllColumns(obj, stmt, 1);
698             stmt.executeUpdate();
699         }
700         Variant key = info.getKey(obj);
701         putToCache(info.name, key, obj);
702         saveRelations(info, obj);
703         return key;
704     }
705 
706     /// Persist the given transient instance.
707     override void persist(Object obj)
708     {
709         auto info = metaData.findEntityForObject(obj);
710         enforceHelper!TransientObjectException(info.isKeySet(obj),
711                 "Cannot persist entity w/o key assigned");
712         string query = metaData.generateInsertAllFieldsForEntity(dialect, info);
713         PreparedStatement stmt = conn.prepareStatement(query);
714         scope (exit)
715             stmt.close();
716         metaData.writeAllColumns(obj, stmt, 1);
717         stmt.executeUpdate();
718         Variant key = info.getKey(obj);
719         putToCache(info.name, key, obj);
720         saveRelations(info, obj);
721     }
722 
723     override void update(Object obj)
724     {
725         auto info = metaData.findEntityForObject(obj);
726         enforceHelper!TransientObjectException(info.isKeySet(obj),
727                 "Cannot persist entity w/o key assigned");
728         string query = metaData.generateUpdateForEntity(dialect, info);
729         //writeln("Query: " ~ query);
730         {
731             PreparedStatement stmt = conn.prepareStatement(query);
732             scope (exit)
733                 stmt.close();
734             const int columnCount = metaData.writeAllColumns(obj, stmt, 1, true);
735             info.keyProperty.writeFunc(obj, stmt, columnCount + 1);
736             stmt.executeUpdate();
737         }
738         updateRelations(info, obj);
739     }
740 
741     // renamed from Session.delete since delete is D keyword
742     override void remove(Object obj)
743     {
744         auto info = metaData.findEntityForObject(obj);
745         deleteRelations(info, obj);
746         string query = "DELETE FROM " ~ dialect.quoteIfNeeded(
747                 info.tableName) ~ " WHERE " ~ dialect.quoteIfNeeded(info.getKeyProperty()
748                 .columnName) ~ "=?";
749         PreparedStatement stmt = conn.prepareStatement(query);
750         info.getKeyProperty().writeFunc(obj, stmt, 1);
751         stmt.executeUpdate();
752     }
753 
754     /// Create a new instance of Query for the given HQL query string
755     override Query createQuery(string queryString)
756     {
757         return new QueryImpl(this, queryString);
758     }
759 }
760 
761 /// Implementation of DStruct SessionFactory
762 class SessionFactoryImpl : SessionFactory
763 {
764     //    Configuration cfg;
765     //    Mapping mapping;
766     //    Settings settings;
767     //    EventListeners listeners;
768     //    SessionFactoryObserver observer;
769     private bool closed;
770     private EntityMetaData metaData;
771     Dialect dialect;
772     DataSource connectionPool;
773 
774     SessionImpl[] activeSessions;
775 
776     DBInfo _dbInfo;
777     override public DBInfo getDBMetaData()
778     {
779         if (_dbInfo is null)
780             _dbInfo = new DBInfo(dialect, metaData);
781         return _dbInfo;
782     }
783 
784     void sessionClosed(SessionImpl session)
785     {
786         foreach (i, item; activeSessions)
787         {
788             if (item == session)
789             {
790                 remove(activeSessions, i);
791             }
792         }
793     }
794 
795     this(EntityMetaData metaData, Dialect dialect, DataSource connectionPool)
796     {
797         //writeln("Creating session factory");
798         this.metaData = metaData;
799         this.dialect = dialect;
800         this.connectionPool = connectionPool;
801     }
802 
803     //    this(Configuration cfg, Mapping mapping, Settings settings, EventListeners listeners, SessionFactoryObserver observer) {
804     //        this.cfg = cfg;
805     //        this.mapping = mapping;
806     //        this.settings = settings;
807     //        this.listeners = listeners;
808     //        this.observer = observer;
809     //        if (observer !is null)
810     //            observer.sessionFactoryCreated(this);
811     //    }
812     private void checkClosed()
813     {
814         enforceHelper!SessionException(!closed, "Session factory is closed");
815     }
816 
817     override void close()
818     {
819         //writeln("Closing session factory");
820         checkClosed();
821         closed = true;
822         //        if (observer !is null)
823         //            observer.sessionFactoryClosed(this);
824         // TODO:
825     }
826 
827     bool isClosed()
828     {
829         return closed;
830     }
831 
832     Session openSession()
833     {
834         checkClosed();
835         SessionImpl session = new SessionImpl(this, metaData, dialect, connectionPool);
836         activeSessions ~= session;
837         return session;
838     }
839 }
840 
841 struct ObjectList
842 {
843     Object[] list;
844     void add(Object obj)
845     {
846         foreach (v; list)
847         {
848             if (v == obj)
849             {
850                 return; // avoid duplicates
851             }
852         }
853         list ~= obj;
854     }
855 
856     @property int length()
857     {
858         return cast(int) list.length;
859     }
860 
861     ref Object opIndex(size_t index)
862     {
863         return list[index];
864     }
865 }
866 
867 Variant normalize(Variant v)
868 {
869     // variants of different type are not equal, normalize to make sure that keys Variant(1) and Variant(1L) are the same
870     if (v.convertsTo!long)
871         return Variant(v.get!long);
872     else if (v.convertsTo!ulong)
873         return Variant(v.get!ulong);
874     return Variant(v.toString());
875 }
876 
877 /// task to load reference entity
878 class PropertyLoadItem
879 {
880     const PropertyInfo property;
881     private ObjectList[Variant] _map; // ID to object list
882     this(const PropertyInfo property)
883     {
884         this.property = property;
885     }
886 
887     @property ref ObjectList[Variant] map()
888     {
889         return _map;
890     }
891 
892     @property Variant[] keys()
893     {
894         return _map.keys;
895     }
896 
897     @property int length()
898     {
899         return cast(int) _map.length;
900     }
901 
902     ObjectList* opIndex(Variant key)
903     {
904         Variant id = normalize(key);
905         if ((id in _map) is null)
906         {
907             _map[id] = ObjectList();
908         }
909         //assert(length > 0);
910         return &_map[id];
911     }
912 
913     void add(ref Variant id, Object obj)
914     {
915         auto item = opIndex(id);
916         item.add(obj);
917         //assert(item.length == opIndex(id).length);
918     }
919 
920     string createCommaSeparatedKeyList()
921     {
922         assert(map.length > 0);
923         return .createCommaSeparatedKeyList(map.keys);
924     }
925 }
926 
927 string createKeySQL(Variant id)
928 {
929     if (id.convertsTo!long || id.convertsTo!ulong)
930     {
931         return id.toString();
932     }
933     else
934     {
935         return "'" ~ id.toString() ~ "'";
936     }
937 }
938 
939 string createKeyPairSQL(Variant id1, Variant id2)
940 {
941     return "(" ~ createKeySQL(id1) ~ ", " ~ createKeySQL(id2) ~ ")";
942 }
943 
944 string createCommaSeparatedKeyList(Variant[] list)
945 {
946     assert(list.length > 0);
947     string res;
948     foreach (v; list)
949     {
950         if (res.length > 0)
951             res ~= ", ";
952         res ~= createKeySQL(v);
953     }
954     return res;
955 }
956 
957 /// task to load reference entity
958 class EntityCollections
959 {
960     private ObjectList[Variant] _map; // ID to object list
961     @property ref ObjectList[Variant] map()
962     {
963         return _map;
964     }
965 
966     @property Variant[] keys()
967     {
968         return _map.keys;
969     }
970 
971     @property int length()
972     {
973         return cast(int) _map.length;
974     }
975 
976     ref Object[] opIndex(Variant key)
977     {
978         //writeln("searching for key " ~ key.toString);
979         Variant id = normalize(key);
980         if ((id in _map) is null)
981         {
982             //writeln("creating new item");
983             _map[id] = ObjectList();
984         }
985         //assert(length > 0);
986         //writeln("returning item");
987         return _map[id].list;
988     }
989 
990     void add(ref Variant id, Object obj)
991     {
992         auto item = opIndex(id);
993         //writeln("item count = " ~ to!string(item.length));
994         item ~= obj;
995     }
996 }
997 
998 class PropertyLoadMap
999 {
1000     private PropertyLoadItem[const PropertyInfo] _map;
1001     PropertyLoadItem opIndex(const PropertyInfo prop)
1002     {
1003         if ((prop in _map) is null)
1004         {
1005             _map[prop] = new PropertyLoadItem(prop);
1006         }
1007         assert(_map.length > 0);
1008         return _map[prop];
1009     }
1010 
1011     this()
1012     {
1013     }
1014 
1015     this(PropertyLoadMap plm)
1016     {
1017         foreach (k; plm.keys)
1018         {
1019             auto pli = plm[k];
1020             foreach (plik; pli.map.keys)
1021             {
1022                 foreach (obj; pli.map[plik].list.dup)
1023                 {
1024                     add(k, plik, obj);
1025                 }
1026             }
1027         }
1028     }
1029 
1030     PropertyLoadItem remove(const PropertyInfo pi)
1031     {
1032         PropertyLoadItem item = _map[pi];
1033         _map.remove(pi);
1034         return item;
1035     }
1036 
1037     @property ref PropertyLoadItem[const PropertyInfo] map()
1038     {
1039         return _map;
1040     }
1041 
1042     @property int length()
1043     {
1044         return cast(int) _map.length;
1045     }
1046 
1047     @property const(PropertyInfo)[] keys()
1048     {
1049         return _map.keys;
1050     }
1051 
1052     final void add(const PropertyInfo property, Variant id, Object obj)
1053     {
1054         auto item = opIndex(property);
1055         item.add(id, obj);
1056     }
1057 }
1058 
1059 class QueryImpl : Query
1060 {
1061     SessionImpl sess;
1062     ParsedQuery query;
1063     ParameterValues params;
1064     this(SessionImpl sess, string queryString)
1065     {
1066         this.sess = sess;
1067         //writeln("QueryImpl(): HQL: " ~ queryString);
1068         QueryParser parser = new QueryParser(sess.metaData, queryString);
1069         //writeln("parsing");
1070         this.query = parser.makeSQL(sess.dialect);
1071         //writeln("SQL: " ~ this.query.sql);
1072         params = query.createParams();
1073         //writeln("exiting QueryImpl()");
1074     }
1075 
1076     override string getQueryString()
1077     {
1078         return query.hql;
1079     }
1080 
1081     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
1082     override Object uniqueObject()
1083     {
1084         return uniqueResult(cast(Object) null);
1085     }
1086 
1087     /// Convenience method to return a single instance that matches the query, or null if the query returns no results. Reusing existing buffer.
1088     override Object uniqueObject(Object obj)
1089     {
1090         Object[] rows = listObjects(obj);
1091         if (rows == null)
1092             return null;
1093         enforceHelper!NonUniqueResultException(rows.length == 1,
1094                 "Query returned more than one object: " ~ getQueryString());
1095         return rows[0];
1096     }
1097 
1098     /// Convenience method to return a single instance that matches the query, or null if the query returns no results.
1099     override Variant[] uniqueRow()
1100     {
1101         Variant[][] rows = listRows();
1102         if (rows == null)
1103             return null;
1104         enforceHelper!NonUniqueResultException(rows.length == 1,
1105                 "Query returned more than one row: " ~ getQueryString());
1106         return rows[0];
1107     }
1108 
1109     private FromClauseItem findRelation(FromClauseItem from, const PropertyInfo prop)
1110     {
1111         for (int i = 0; i < query.from.length; i++)
1112         {
1113             FromClauseItem f = query.from[i];
1114             if (f.base == from && f.baseProperty == prop)
1115                 return f;
1116             if (f.entity == prop.referencedEntity && from.base == f)
1117                 return f;
1118         }
1119         return null;
1120     }
1121 
1122     private Object readRelations(Object objectContainer, DataSetReader r, PropertyLoadMap loadMap)
1123     {
1124         Object[] relations = new Object[query.select.length];
1125         //writeln("select clause len = " ~ to!string(query.select.length));
1126         // read all related objects from DB row
1127         for (int i = 0; i < query.select.length; i++)
1128         {
1129             FromClauseItem from = query.select[i].from;
1130             //writeln("reading " ~ from.entityName);
1131             Object row;
1132             if (!from.entity.isKeyNull(r, from.startColumn))
1133             {
1134                 //writeln("key is not null");
1135                 Variant key = from.entity.getKey(r, from.startColumn);
1136                 //writeln("key is " ~ key.toString);
1137                 row = sess.peekFromCache(from.entity.name, key);
1138                 if (row is null)
1139                 {
1140                     //writeln("row not found in cache");
1141                     row = (objectContainer !is null && i == 0) ? objectContainer : from.entity.createEntity();
1142                     //writeln("reading all columns");
1143                     sess.metaData.readAllColumns(row, r, from.startColumn);
1144                     sess.putToCache(from.entity.name, key, row);
1145                 }
1146                 else if (objectContainer !is null)
1147                 {
1148                     //writeln("copying all properties to existing container");
1149                     from.entity.copyAllProperties(objectContainer, row);
1150                 }
1151             }
1152             relations[i] = row;
1153         }
1154         //writeln("fill relations...");
1155         // fill relations
1156         for (int i = 0; i < query.select.length; i++)
1157         {
1158             if (relations[i] is null)
1159                 continue;
1160             FromClauseItem from = query.select[i].from;
1161             auto ei = from.entity;
1162             for (int j = 0; j < ei.length; j++)
1163             {
1164                 auto pi = ei[j];
1165                 if (pi.oneToOne || pi.manyToOne)
1166                 {
1167                     static if (TRACE_REFS)
1168                         writeln("updating relations for " ~ from.pathString ~ "." ~ pi.propertyName);
1169                     FromClauseItem rfrom = findRelation(from, pi);
1170                     if (rfrom !is null && rfrom.selectIndex >= 0)
1171                     {
1172                         Object rel = relations[rfrom.selectIndex];
1173                         pi.setObjectFunc(relations[i], rel);
1174                     }
1175                     else
1176                     {
1177                         if (pi.columnName !is null)
1178                         {
1179                             static if (TRACE_REFS)
1180                                 writeln("relation " ~ pi.propertyName ~ " has column name");
1181                             if (r.isNull(from.startColumn + pi.columnOffset))
1182                             {
1183                                 // FK is null, set NULL to field
1184                                 pi.setObjectFunc(relations[i], null);
1185                                 static if (TRACE_REFS)
1186                                     writeln("relation " ~ pi.propertyName ~ " has null FK");
1187                             }
1188                             else
1189                             {
1190                                 Variant id = r.getVariant(from.startColumn + pi.columnOffset);
1191                                 Object existing = sess.peekFromCache(pi.referencedEntity.name, id);
1192                                 if (existing !is null)
1193                                 {
1194                                     static if (TRACE_REFS)
1195                                         writeln("existing relation found in cache");
1196                                     pi.setObjectFunc(relations[i], existing);
1197                                 }
1198                                 else
1199                                 {
1200                                     // FK is not null
1201                                     if (pi.lazyLoad)
1202                                     {
1203                                         // lazy load
1204                                         static if (TRACE_REFS)
1205                                             writeln("scheduling lazy load for " ~ from.pathString ~ "."
1206                                                     ~ pi.propertyName ~ " with FK " ~ id.toString);
1207                                         LazyObjectLoader loader = new LazyObjectLoader(sess.accessor,
1208                                                 pi, id);
1209                                         pi.setObjectDelegateFunc(relations[i], &loader.load);
1210                                     }
1211                                     else
1212                                     {
1213                                         // delayed load
1214                                         static if (TRACE_REFS)
1215                                             writeln("relation " ~ pi.propertyName ~ " with FK " ~ id.toString()
1216                                                     ~ " will be loaded later");
1217                                         loadMap.add(pi, id, relations[i]); // to load later
1218                                     }
1219                                 }
1220                             }
1221                         }
1222                         else
1223                         {
1224                             // TODO:
1225                             assert(false,
1226                                     "relation " ~ pi.propertyName
1227                                     ~ " has no column name. To be implemented.");
1228                         }
1229                     }
1230                 }
1231                 else if (pi.oneToMany || pi.manyToMany)
1232                 {
1233                     Variant id = ei.getKey(relations[i]);
1234                     if (pi.lazyLoad)
1235                     {
1236                         // lazy load
1237                         static if (TRACE_REFS)
1238                             writeln("creating lazy loader for " ~ from.pathString
1239                                     ~ "." ~ pi.propertyName ~ " by FK " ~ id.toString);
1240                         LazyCollectionLoader loader = new LazyCollectionLoader(sess.accessor,
1241                                 pi, id);
1242                         pi.setCollectionDelegateFunc(relations[i], &loader.load);
1243                     }
1244                     else
1245                     {
1246                         // delayed load
1247                         static if (TRACE_REFS)
1248                             writeln("Relation " ~ from.pathString ~ "." ~ pi.propertyName
1249                                     ~ " will be loaded later by FK " ~ id.toString);
1250                         loadMap.add(pi, id, relations[i]); // to load later
1251                     }
1252                 }
1253             }
1254         }
1255         return relations[0];
1256     }
1257 
1258     /// Return the query results as a List of entity objects
1259     override Object[] listObjects()
1260     {
1261         return listObjects(null);
1262     }
1263 
1264     private void delayedLoadRelations(PropertyLoadMap loadMap)
1265     {
1266         loadMap = new PropertyLoadMap(loadMap);
1267 
1268         auto types = loadMap.keys;
1269         static if (TRACE_REFS)
1270             writeln("delayedLoadRelations " ~ to!string(loadMap.length));
1271 
1272         foreach (pi; types)
1273         {
1274             static if (TRACE_REFS)
1275                 writeln("delayedLoadRelations " ~ pi.entity.name ~ "." ~ pi.propertyName);
1276             assert(pi.referencedEntity !is null);
1277             auto map = loadMap.remove(pi);
1278             if (map.length == 0)
1279                 continue;
1280             //writeln("delayedLoadRelations " ~ pi.entity.name ~ "." ~ pi.propertyName);
1281             string keys = map.createCommaSeparatedKeyList();
1282             if (pi.oneToOne || pi.manyToOne)
1283             {
1284                 if (pi.columnName !is null)
1285                 {
1286                     Variant[] unknownKeys;
1287                     Object[] list = sess.lookupCache(pi.referencedEntity.name,
1288                             map.keys, unknownKeys);
1289                     if (unknownKeys.length > 0)
1290                     {
1291                         string hql = "FROM " ~ pi.referencedEntity.name ~ " WHERE "
1292                             ~ pi.referencedEntity.keyProperty.propertyName ~ " IN (" ~ createCommaSeparatedKeyList(
1293                                     unknownKeys) ~ ")";
1294                         static if (TRACE_REFS)
1295                             writeln(
1296                                     "delayedLoadRelations: loading "
1297                                     ~ pi.propertyName ~ " HQL: " ~ hql);
1298                         QueryImpl q = cast(QueryImpl) sess.createQuery(hql);
1299                         Object[] fromDB = q.listObjects(null, loadMap);
1300                         list ~= fromDB;
1301                         static if (TRACE_REFS)
1302                             writeln("delayedLoadRelations: objects loaded " ~ to!string(
1303                                     fromDB.length));
1304                     }
1305                     else
1306                     {
1307                         static if (TRACE_REFS)
1308                             writeln("all objects found in cache");
1309                     }
1310                     static if (TRACE_REFS)
1311                         writeln("delayedLoadRelations: updating");
1312                     foreach (rel; list)
1313                     {
1314                         static if (TRACE_REFS)
1315                             writeln(
1316                                     "delayedLoadRelations: reading key from "
1317                                     ~ pi.referencedEntity.name);
1318                         Variant key = pi.referencedEntity.getKey(rel);
1319                         //writeln("map length before: " ~ to!string(map.length));
1320                         auto objectsToUpdate = map[key].list;
1321                         //writeln("map length after: " ~ to!string(map.length));
1322                         //writeln("updating relations with key " ~ key.toString() ~ " (" ~ to!string(objectsToUpdate.length) ~ ")");
1323                         foreach (obj; objectsToUpdate)
1324                         {
1325                             pi.setObjectFunc(obj, rel);
1326                         }
1327                     }
1328                 }
1329                 else
1330                 {
1331                     assert(false,
1332                             "Delayed loader for non-join column is not yet implemented for OneToOne and ManyToOne");
1333                 }
1334             }
1335             else if (pi.oneToMany || pi.manyToMany)
1336             {
1337                 string hql = "FROM " ~ pi.referencedEntity.name ~ " WHERE " ~ pi.referencedPropertyName ~ "."
1338                     ~ pi.referencedEntity.keyProperty.propertyName ~ " IN (" ~ keys ~ ")";
1339                 static if (TRACE_REFS)
1340                     writeln("delayedLoadRelations: loading " ~ pi.propertyName ~ " HQL: " ~ hql);
1341                 QueryImpl q = cast(QueryImpl) sess.createQuery(hql);
1342                 assert(q !is null);
1343                 Object[] list = q.listObjects(null, loadMap);
1344                 static if (TRACE_REFS)
1345                     writeln("delayedLoadRelations oneToMany/manyToMany: objects loaded " ~ to!string(
1346                             list.length));
1347                 EntityCollections collections = new EntityCollections();
1348                 // group by referenced PK
1349                 foreach (rel; list)
1350                 {
1351                     static if (TRACE_REFS)
1352                         writeln("delayedLoadRelations oneToMany/manyToMany: reading reference from "
1353                                 ~ pi.referencedEntity.name ~ "." ~ pi.referencedProperty.propertyName
1354                                 ~ " joinColumn=" ~ pi.referencedProperty.columnName);
1355                     assert(pi.referencedProperty.manyToOne,
1356                             "property referenced from OneToMany should be ManyToOne");
1357                     assert(pi.referencedProperty.getObjectFunc !is null);
1358                     assert(rel !is null);
1359                     //writeln("delayedLoadRelations oneToMany: reading object " ~ rel.classinfo.toString);
1360                     Object obj = pi.referencedProperty.getObjectFunc(rel);
1361                     //writeln("delayedLoadRelations oneToMany: object is read");
1362                     if (obj !is null)
1363                     {
1364                         //writeln("delayedLoadRelations oneToMany: object is not null");
1365                         //writeln("pi.entity.name=" ~ pi.entity.name ~ ", obj is " ~ obj.classinfo.toString);
1366                         //writeln("obj = " ~ obj.toString);
1367                         //writeln("pi.entity.keyProperty=" ~ pi.entity.keyProperty.propertyName);
1368                         //assert(pi.entity.keyProperty.getFunc !is null);
1369                         //Variant k = pi.entity.keyProperty.getFunc(obj);
1370                         //writeln("key=" ~ k.toString);
1371                         Variant key = pi.entity.getKey(obj);
1372                         collections[key] ~= rel;
1373                         //collections.add(k, rel);
1374                     }
1375                 }
1376                 // update objects
1377                 foreach (key; collections.keys)
1378                 {
1379                     auto objectsToUpdate = map[key].list;
1380                     foreach (obj; objectsToUpdate)
1381                     {
1382                         pi.setCollectionFunc(obj, collections[key]);
1383                     }
1384                 }
1385             }
1386         }
1387     }
1388 
1389     /// Return the query results as a List of entity objects
1390     Object[] listObjects(Object placeFirstObjectHere)
1391     {
1392         PropertyLoadMap loadMap = new PropertyLoadMap();
1393         return listObjects(placeFirstObjectHere, loadMap);
1394     }
1395 
1396     /// Return the query results as a List of entity objects
1397     Object[] listObjects(Object placeFirstObjectHere, PropertyLoadMap loadMap)
1398     {
1399         static if (TRACE_REFS)
1400             writeln("Entering listObjects " ~ query.hql);
1401         auto ei = query.entity;
1402         enforceHelper!SessionException(ei !is null,
1403                 "No entity expected in result of query " ~ getQueryString());
1404         params.checkAllParametersSet();
1405         sess.checkClosed();
1406 
1407         Object[] res;
1408 
1409         PreparedStatement stmt = sess.conn.prepareStatement(query.sql);
1410         scope (exit)
1411             stmt.close();
1412         params.applyParams(stmt);
1413         ResultSet rs = stmt.executeQuery();
1414         assert(query.select !is null && query.select.length > 0);
1415         // const int startColumn = query.select[0].from.startColumn;
1416         {
1417             scope (exit)
1418                 rs.close();
1419             while (rs.next())
1420             {
1421                 Object row = readRelations(res.length > 0 ? null : placeFirstObjectHere,
1422                         rs, loadMap);
1423                 if (row !is null)
1424                     res ~= row;
1425             }
1426         }
1427         if (loadMap.length > 0)
1428         {
1429             static if (TRACE_REFS)
1430                 writeln("relation properties scheduled for load: loadMap.length == " ~ to!string(
1431                         loadMap.length));
1432             delayedLoadRelations(loadMap);
1433         }
1434         static if (TRACE_REFS)
1435             writeln("Exiting listObjects " ~ query.hql);
1436         return res.length > 0 ? res : null;
1437     }
1438 
1439     /// Return the query results as a List which each row as Variant array
1440     override Variant[][] listRows()
1441     {
1442         params.checkAllParametersSet();
1443         sess.checkClosed();
1444 
1445         Variant[][] res;
1446 
1447         //writeln("SQL: " ~ query.sql);
1448         PreparedStatement stmt = sess.conn.prepareStatement(query.sql);
1449         scope (exit)
1450             stmt.close();
1451         params.applyParams(stmt);
1452         ResultSet rs = stmt.executeQuery();
1453         scope (exit)
1454             rs.close();
1455         while (rs.next())
1456         {
1457             Variant[] row = new Variant[query.colCount];
1458             for (int i = 1; i <= query.colCount; i++)
1459                 row[i - 1] = rs.getVariant(i);
1460             res ~= row;
1461         }
1462         return res.length > 0 ? res : null;
1463     }
1464 
1465     /// Bind a value to a named query parameter.
1466     override protected Query setParameterVariant(string name, Variant val)
1467     {
1468         params.setParameter(name, val);
1469         return this;
1470     }
1471 }
1472 
1473 class LazyObjectLoader
1474 {
1475     const PropertyInfo pi;
1476     Variant id;
1477     SessionAccessor sess;
1478     this(SessionAccessor sess, const PropertyInfo pi, Variant id)
1479     {
1480         static if (TRACE_REFS)
1481             writeln("Created lazy loader for " ~ pi.referencedEntityName ~ " with id " ~ id
1482                     .toString);
1483         this.pi = pi;
1484         this.id = id;
1485         this.sess = sess;
1486     }
1487 
1488     Object load()
1489     {
1490         static if (TRACE_REFS)
1491             writeln("LazyObjectLoader.load()");
1492         static if (TRACE_REFS)
1493             writeln("lazy loading of " ~ pi.referencedEntityName ~ " with id " ~ id.toString);
1494         return sess.get().loadObject(pi.referencedEntityName, id);
1495     }
1496 }
1497 
1498 class LazyCollectionLoader
1499 {
1500     const PropertyInfo pi;
1501     Variant fk;
1502     SessionAccessor sess;
1503     this(SessionAccessor sess, const PropertyInfo pi, Variant fk)
1504     {
1505         assert(!pi.oneToMany || (pi.referencedEntity !is null && pi.referencedProperty !is null),
1506                 "LazyCollectionLoader: No referenced property specified for OneToMany foreign key column");
1507         static if (TRACE_REFS)
1508             writeln("Created lazy loader for collection for references "
1509                     ~ pi.entity.name ~ "." ~ pi.propertyName ~ " by id " ~ fk.toString);
1510         this.pi = pi;
1511         this.fk = fk;
1512         this.sess = sess;
1513     }
1514 
1515     Object[] load()
1516     {
1517         static if (TRACE_REFS)
1518             writeln("LazyObjectLoader.load()");
1519         static if (TRACE_REFS)
1520             writeln(
1521                     "lazy loading of references " ~ pi.entity.name ~ "."
1522                     ~ pi.propertyName ~ " by id " ~ fk.toString);
1523         Object[] res = sess.get().loadReferencedObjects(pi.entity, pi.propertyName, fk);
1524         return res;
1525     }
1526 }