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