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/type.d.
8  *
9  * This module contains declarations of property type description 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.type;
16 
17 import std.datetime;
18 import std.stdio;
19 import std.traits;
20 import std.typecons;
21 
22 import ddbc.core;
23 
24 
25 // convenient aliases for Nullable types
26 
27 alias Byte = Nullable!byte;
28 alias Ubyte = Nullable!ubyte;
29 alias Short = Nullable!short;
30 alias Ushort = Nullable!ushort;
31 alias Int = Nullable!int;
32 alias Uint = Nullable!uint;
33 alias Long = Nullable!long;
34 alias Ulong = Nullable!ulong;
35 alias Float = Nullable!float;
36 alias Double = Nullable!double;
37 alias NullableDateTime = Nullable!DateTime;
38 alias NullableDate = Nullable!Date;
39 alias NullableTimeOfDay = Nullable!TimeOfDay;
40 
41 /// Wrapper around string, to distinguish between Null and NotNull fields: string is NotNull, String is Null -- same interface as in Nullable
42 // Looks ugly, but I tried `typedef string String`, but it is deprecated; `alias string String` cannot be distinguished from just string. How to define String better?
43 struct String
44 {
45     string _value;
46 
47 /**
48 Returns $(D true) if and only if $(D this) is in the null state.
49 */
50     @property bool isNull() const pure nothrow @safe
51     {
52         return _value is null;
53     }
54     
55 /**
56 Forces $(D this) to the null state.
57 */
58     void nullify()
59     {
60         _value = null;
61     }
62     
63     alias _value this;
64 }
65 
66 unittest {
67     string a = "abc";
68     String b;
69     assert(b.isNull);
70     assert(b is null);
71     b = a;
72     assert(b == "abc");
73     b.nullify();
74     a = b;
75     assert(a is null);
76     b = "123";
77     b = null;
78 }
79 
80 //struct String2
81 //{
82 //    private string _value;
83 //    
84 //    /**
85 //Returns $(D true) if and only if $(D this) is in the null state.
86 //*/
87 //    @property bool isNull() const pure nothrow @safe
88 //    {
89 //        return _value is null;
90 //    }
91 //    
92 //    /**
93 //Forces $(D this) to the null state.
94 //*/
95 //    void nullify()
96 //    {
97 //        _value = null;
98 //    }
99 //    
100 //    alias _value this;
101 //}
102 //
103 //unittest {
104 //    string a = "abc";
105 //    String2 b;
106 //    assert(b.isNull);
107 //    assert(b is null);
108 //    b = a;
109 //    assert(b == "abc");
110 //    b.nullify();
111 //    a = b;
112 //    assert(a is null);
113 //    b = "123";
114 //    b = null;
115 //
116 //    class C1 {
117 //        String2 nullable_string_field;
118 //    }
119 //    C1 entity = new C1;
120 //    String2 nv;
121 //    entity.nullable_string_field = (true ? String2("abc") : nv); 
122 //    entity.nullable_string_field = (false ? String2("abc") : nv); 
123 //}
124 
125 // Exception classes
126 
127 /// base class for all HibernateD exceptions
128 class HibernatedException : Exception {
129     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
130     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
131 }
132 
133 /// Thrown when the user passes a transient instance to a Session method that expects a persistent instance. 
134 class TransientObjectException : HibernatedException {
135     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
136     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
137 }
138 
139 /// Something went wrong in the cache
140 class CacheException : HibernatedException {
141     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
142     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
143 }
144 
145 /// Thrown when the (illegal) value of a property can not be persisted. There are two main causes: a property declared not-null="true" is null or an association references an unsaved transient instance 
146 class PropertyValueException : HibernatedException {
147     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
148     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
149 }
150 
151 /// A problem occurred translating a Hibernate query to SQL due to invalid query syntax, etc. 
152 class QueryException : HibernatedException {
153     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
154     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
155 }
156 
157 /// Exception thrown when there is a syntax error in the HQL. 
158 class QuerySyntaxException : QueryException {
159     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
160     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
161 }
162 
163 /// Parameter invalid or not found in the query
164 class QueryParameterException : QueryException {
165     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
166     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
167 }
168 
169 /// Indicates that a transaction could not be begun, committed or rolled back. 
170 class TransactionException : HibernatedException {
171     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
172     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
173 }
174 
175 /// Indicates access to unfetched data outside of a session context. For example, when an uninitialized proxy or collection is accessed after the session was closed. 
176 class LazyInitializationException : HibernatedException {
177     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
178     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
179 }
180 
181 /// An exception that usually occurs as a result of something screwy in the O-R mappings. 
182 class MappingException : HibernatedException {
183     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
184     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
185 }
186 
187 /// Thrown when Hibernate could not resolve an object by id, especially when loading an association.
188 class UnresolvableObjectException : HibernatedException {
189     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
190     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
191 }
192 
193 /// An exception occurs when query result is expected but object is not found in DB.
194 class ObjectNotFoundException : UnresolvableObjectException {
195     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
196     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
197 }
198 
199 /// Thrown when the user tries to do something illegal with a deleted object. 
200 class ObjectDeletedException : UnresolvableObjectException {
201     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
202     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
203 }
204 
205 /// Thrown when the user calls a method of a Session that is in an inappropropriate state for the given call (for example, the the session is closed or disconnected). 
206 class SessionException : HibernatedException {
207     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
208     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
209 }
210 
211 /// Thrown when the application calls Query.uniqueResult() and the query returned more than one result. Unlike all other Hibernate exceptions, this one is recoverable! 
212 class NonUniqueResultException : HibernatedException {
213     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
214     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
215 }
216 
217 
218 /// Base class for HibernateD property types
219 class Type {
220 public:
221     immutable SqlType getSqlType() { return SqlType.OTHER; }
222     immutable string getName() { return ""; }
223     //immutable TypeInfo getReturnedClass() { return null; }
224 }
225 
226 class BooleanType : Type {
227 public:
228     override immutable string getName() { return "Boolean"; }
229     override immutable SqlType getSqlType() { return SqlType.BOOLEAN; }
230 }
231 
232 
233 class StringType : Type {
234 private:
235     int _length;
236 public:
237     this(int len = 0) { _length = len; }
238     @property int length() { return _length; }
239     override immutable SqlType getSqlType() { return SqlType.VARCHAR; }
240     override immutable string getName() { return "String"; }
241     //override immutable TypeInfo getReturnedClass() { return typeid(string); }
242 
243 }
244 
245 class NumberType : Type {
246 protected:
247     int _length;
248     bool _unsigned;
249     SqlType _type;
250 public:
251     this(int len, bool unsigned, SqlType type) {
252         _length = len;
253         _unsigned = unsigned;
254         _type = type;
255     }
256     @property int length() { return _length; }
257     @property bool unsigned() { return _unsigned; }
258     override immutable SqlType getSqlType() { return _type; }
259     override immutable string getName() { return "Integer"; }
260 }
261 
262 class DateTimeType : Type {
263 public:
264     override immutable string getName() { return "DateTime"; }
265     //override immutable TypeInfo getReturnedClass() { return typeid(DateTime); }
266     override immutable SqlType getSqlType() { return SqlType.DATETIME; }
267 }
268 
269 class DateType : Type {
270 public:
271     override immutable string getName() { return "Date"; }
272     //override immutable TypeInfo getReturnedClass() { return typeid(Date); }
273     override immutable SqlType getSqlType() { return SqlType.DATE; }
274 }
275 
276 class TimeType : Type {
277 public:
278     override immutable string getName() { return "Time"; }
279     //override immutable TypeInfo getReturnedClass() { return typeid(TimeOfDay); }
280     override immutable SqlType getSqlType() { return SqlType.TIME; }
281 }
282 
283 class ByteArrayBlobType : Type {
284 public:
285     override immutable string getName() { return "ByteArray"; }
286     //override immutable TypeInfo getReturnedClass() { return typeid(byte[]); }
287     override immutable SqlType getSqlType() { return SqlType.BLOB; }
288 }
289 
290 class UbyteArrayBlobType : Type {
291 public:
292     override immutable string getName() { return "UbyteArray"; }
293     //override immutable TypeInfo getReturnedClass() { return typeid(ubyte[]); }
294     override immutable SqlType getSqlType() { return SqlType.BLOB; }
295 }
296 
297 // TODO
298 class EntityType : Type {
299     private string name;
300     private immutable TypeInfo_Class classType;
301 public:
302     this(immutable TypeInfo_Class classType, string className) {
303         this.classType = classType;
304         this.name = className;
305     }
306     override immutable string getName() { return name; }
307     //override immutable TypeInfo getReturnedClass() { return null; }
308 }
309 
310 /**
311  * Lazy entity loader. 
312  * 
313  * 
314  */
315 struct Lazy(T) {
316     alias delegate_t = Object delegate();
317     private T _value;
318     private delegate_t _delegate;
319 
320     T opCall() {
321         return get();
322     }
323 
324     @property bool loaded() {
325         return _delegate is null;
326     }
327 
328     T get() {
329         //writeln("Lazy! opCall()");
330         //writeln("Lazy! opCall() delegate " ~ (_delegate !is null ? "is not null" : "is null"));
331         if (_delegate !is null) {
332             //writeln("Lazy! opCall() Delegate is set! Calling it to get value");
333             T value = cast(T)_delegate();
334             //writeln("Lazy! opCall() delegate returned value " ~ value.classinfo.toString);
335             opAssign(value);
336         } else {
337             //writeln("Lazy! opCall() Returning value instantly");
338         }
339         return _value;
340     }
341 
342     T opCast(TT)() if (is(TT == T)) {
343         return get();
344     }
345 
346     T opAssign(T v) {
347         //writeln("Lazy! opAssign(value)");
348         _value = v;
349         _delegate = null;
350         return _value;
351     }
352 
353     ref Lazy!T opAssign(ref Lazy!T v) {
354         //writeln("Lazy! opAssign(value)");
355         _value = v._value;
356         _delegate = v._delegate;
357         return this;
358     }
359     
360     void opAssign(delegate_t lazyLoader) {
361         //writeln("Lazy! opAssign(delegate)");
362         _delegate = lazyLoader;
363         _value = null;
364     }
365 
366     alias get this;
367 }
368 
369 /**
370  * Lazy entity collection loader. 
371  */
372 struct LazyCollection(T) {
373     alias delegate_t = Object[] delegate();
374     private T[] _value;
375     private delegate_t _delegate;
376     
377     T[] opCall() {
378         return get();
379     }
380     
381     @property bool loaded() {
382         return _delegate is null;
383     }
384     
385     ref T[] get() {
386         //writeln("Lazy! opCall()");
387         //writeln("Lazy! opCall() delegate " ~ (_delegate !is null ? "is not null" : "is null"));
388         if (_delegate !is null) {
389             //writeln("Lazy! opCall() Delegate is set! Calling it to get value");
390             T[] value = cast(T[])_delegate();
391             //writeln("Lazy! opCall() delegate returned value " ~ value.classinfo.toString);
392             opAssign(value);
393         } else {
394             //writeln("Lazy! opCall() Returning value instantly");
395         }
396         return _value;
397     }
398     
399     TT opCast(TT)() if (isArray!TT && isImplicitlyConvertible!(typeof(TT.init[0]), Object)) {
400         return cast(TT)get();
401     }
402     
403     T[] opAssign(T[] v) {
404         //writeln("Lazy! opAssign(value)");
405         _value = v;
406         _delegate = null;
407         return _value;
408     }
409     
410     ref LazyCollection!T opAssign(ref LazyCollection!T v) {
411         //writeln("Lazy! opAssign(value)");
412         _value = v._value;
413         _delegate = v._delegate;
414         return this;
415     }
416     
417     void opAssign(delegate_t lazyLoader) {
418         //writeln("Lazy! opAssign(delegate)");
419         _delegate = lazyLoader;
420         _value = null;
421     }
422     
423     alias get this;
424 }
425 
426 unittest {
427 
428     class Foo {
429         string name;
430         this(string v) {
431             name = v;
432         }
433     }
434     struct LazyVar(T) {
435         T a;
436         int b;
437         T opCall() {
438             b++;
439             return a;
440         }
441         alias opCall this;
442     }
443     class TestLazy {
444         LazyVar!Foo l;
445     }
446 
447     auto loader = delegate() {
448         return new Foo("lazy loaded");
449     };
450 
451     Foo res;
452     Lazy!Foo field;
453     res = field();
454     assert(res is null);
455     field = loader;
456     res = field();
457     assert(res.name == "lazy loaded");
458     field = new Foo("another string");
459     res = cast(Foo)field;
460     assert(res.name == "another string");
461 
462     static class Bar {
463         @property Lazy!Foo field;
464     }
465     Bar bar = new Bar();
466     bar.field = new Foo("name1");
467     res = bar.field();
468 
469     LazyVar!string s;
470     s.a = "10";
471     assert(s() == "10");
472     assert(s.b == 1);
473     s.a = "15";
474     assert(s == "15");
475     assert(s.b == 2);
476 
477     import hibernated.metadata;
478     TestLazy tl = new TestLazy();
479 }
480