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/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 dstruct.type;
16 
17 import std.datetime;
18 import std.stdio;
19 import std.traits;
20 import std.typecons;
21 
22 import dstruct.ddbc.core;
23 
24 
25 // convenient aliases for Nullable types
26 
27 alias Nullable!byte Byte;
28 alias Nullable!ubyte Ubyte;
29 alias Nullable!short Short;
30 alias Nullable!ushort Ushort;
31 alias Nullable!int Int;
32 alias Nullable!uint Uint;
33 alias Nullable!long Long;
34 alias Nullable!ulong Ulong;
35 alias Nullable!float Float;
36 alias Nullable!double Double;
37 alias Nullable!DateTime NullableDateTime;
38 alias Nullable!Date NullableDate;
39 alias Nullable!TimeOfDay NullableTimeOfDay;
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 // Exception classes
81 
82 /// base class for all DStruct exceptions
83 class DStructException : Exception {
84     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
85     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
86 }
87 
88 /// Thrown when the user passes a transient instance to a Session method that expects a persistent instance. 
89 class TransientObjectException : DStructException {
90     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
91     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
92 }
93 
94 /// Something went wrong in the cache
95 class CacheException : DStructException {
96     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
97     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
98 }
99 
100 /// 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 
101 class PropertyValueException : DStructException {
102     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
103     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
104 }
105 
106 /// A problem occurred translating a Hibernate query to SQL due to invalid query syntax, etc. 
107 class QueryException : DStructException {
108     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
109     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
110 }
111 
112 /// Exception thrown when there is a syntax error in the HQL. 
113 class QuerySyntaxException : QueryException {
114     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
115     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
116 }
117 
118 /// Parameter invalid or not found in the query
119 class QueryParameterException : QueryException {
120     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
121     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
122 }
123 
124 /// Indicates that a transaction could not be begun, committed or rolled back. 
125 class TransactionException : DStructException {
126     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
127     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
128 }
129 
130 /// 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. 
131 class LazyInitializationException : DStructException {
132     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
133     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
134 }
135 
136 /// An exception that usually occurs as a result of something screwy in the O-R mappings. 
137 class MappingException : DStructException {
138     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
139     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
140 }
141 
142 /// Thrown when Hibernate could not resolve an object by id, especially when loading an association.
143 class UnresolvableObjectException : DStructException {
144     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
145     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
146 }
147 
148 /// An exception occurs when query result is expected but object is not found in DB.
149 class ObjectNotFoundException : UnresolvableObjectException {
150     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
151     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
152 }
153 
154 /// Thrown when the user tries to do something illegal with a deleted object. 
155 class ObjectDeletedException : UnresolvableObjectException {
156     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
157     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
158 }
159 
160 /// 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). 
161 class SessionException : DStructException {
162     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
163     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
164 }
165 
166 /// Thrown when the application calls Query.uniqueResult() and the query returned more than one result. Unlike all other Hibernate exceptions, this one is recoverable! 
167 class NonUniqueResultException : DStructException {
168     this(string msg, string f = __FILE__, size_t l = __LINE__) { super(msg, f, l); }
169     this(Throwable causedBy, string f = __FILE__, size_t l = __LINE__) { super(causedBy.msg, f, l); }
170 }
171 
172 
173 /// Base class for DStruct property types
174 class Type {
175 public:
176     immutable SqlType getSqlType() { return SqlType.OTHER; }
177     immutable string getName() { return ""; }
178     //immutable TypeInfo getReturnedClass() { return null; }
179 }
180 
181 class BooleanType : Type {
182 public:
183     override immutable string getName() { return "Boolean"; }
184     override immutable SqlType getSqlType() { return SqlType.BOOLEAN; }
185 }
186 
187 
188 class StringType : Type {
189 private:
190     int _length;
191 public:
192     this(int len = 0) { _length = len; }
193     @property int length() { return _length; }
194     override immutable SqlType getSqlType() { return SqlType.VARCHAR; }
195     override immutable string getName() { return "String"; }
196     //override immutable TypeInfo getReturnedClass() { return typeid(string); }
197 
198 }
199 
200 class NumberType : Type {
201 protected:
202     int _length;
203     bool _unsigned;
204     SqlType _type;
205 public:
206     this(int len, bool unsigned, SqlType type) {
207         _length = len;
208         _unsigned = unsigned;
209         _type = type;
210     }
211     @property int length() { return _length; }
212     @property bool unsigned() { return _unsigned; }
213     override immutable SqlType getSqlType() { return _type; }
214     override immutable string getName() { return "Integer"; }
215 }
216 
217 class DateTimeType : Type {
218 public:
219     override immutable string getName() { return "DateTime"; }
220     //override immutable TypeInfo getReturnedClass() { return typeid(DateTime); }
221     override immutable SqlType getSqlType() { return SqlType.DATETIME; }
222 }
223 
224 class DateType : Type {
225 public:
226     override immutable string getName() { return "Date"; }
227     //override immutable TypeInfo getReturnedClass() { return typeid(Date); }
228     override immutable SqlType getSqlType() { return SqlType.DATE; }
229 }
230 
231 class TimeType : Type {
232 public:
233     override immutable string getName() { return "Time"; }
234     //override immutable TypeInfo getReturnedClass() { return typeid(TimeOfDay); }
235     override immutable SqlType getSqlType() { return SqlType.TIME; }
236 }
237 
238 class ByteArrayBlobType : Type {
239 public:
240     override immutable string getName() { return "ByteArray"; }
241     //override immutable TypeInfo getReturnedClass() { return typeid(byte[]); }
242     override immutable SqlType getSqlType() { return SqlType.BLOB; }
243 }
244 
245 class UbyteArrayBlobType : Type {
246 public:
247     override immutable string getName() { return "UbyteArray"; }
248     //override immutable TypeInfo getReturnedClass() { return typeid(ubyte[]); }
249     override immutable SqlType getSqlType() { return SqlType.BLOB; }
250 }
251 
252 // TODO
253 class EntityType : Type {
254     private string name;
255     private immutable TypeInfo_Class classType;
256 public:
257     this(immutable TypeInfo_Class classType, string className) {
258         this.classType = classType;
259         this.name = className;
260     }
261     override immutable string getName() { return name; }
262     //override immutable TypeInfo getReturnedClass() { return null; }
263 }
264 
265 /**
266  * Lazy entity loader. 
267  * 
268  * 
269  */
270 struct Lazy(T) {
271     alias Object delegate() delegate_t;
272     private T _value;
273     private delegate_t _delegate;
274 
275     T opCall() {
276         return get();
277     }
278 
279     @property bool loaded() {
280         return _delegate is null;
281     }
282 
283     T get() {
284         //writeln("Lazy! opCall()");
285         //writeln("Lazy! opCall() delegate " ~ (_delegate !is null ? "is not null" : "is null"));
286         if (_delegate !is null) {
287             //writeln("Lazy! opCall() Delegate is set! Calling it to get value");
288             T value = cast(T)_delegate();
289             //writeln("Lazy! opCall() delegate returned value " ~ value.classinfo.toString);
290             opAssign(value);
291         } else {
292             //writeln("Lazy! opCall() Returning value instantly");
293         }
294         return _value;
295     }
296 
297     T opCast(TT)() if (is(TT == T)) {
298         return get();
299     }
300 
301     T opAssign(T v) {
302         //writeln("Lazy! opAssign(value)");
303         _value = v;
304         _delegate = null;
305         return _value;
306     }
307 
308     ref Lazy!T opAssign(ref Lazy!T v) {
309         //writeln("Lazy! opAssign(value)");
310         _value = v._value;
311         _delegate = v._delegate;
312         return this;
313     }
314     
315     void opAssign(delegate_t lazyLoader) {
316         //writeln("Lazy! opAssign(delegate)");
317         _delegate = lazyLoader;
318         _value = null;
319     }
320 
321     alias get this;
322 }
323 
324 /**
325  * Lazy entity collection loader. 
326  */
327 struct LazyCollection(T) {
328     alias Object[] delegate() delegate_t;
329     private T[] _value;
330     private delegate_t _delegate;
331     
332     T[] opCall() {
333         return get();
334     }
335     
336     @property bool loaded() {
337         return _delegate is null;
338     }
339     
340     ref T[] get() {
341         //writeln("Lazy! opCall()");
342         //writeln("Lazy! opCall() delegate " ~ (_delegate !is null ? "is not null" : "is null"));
343         if (_delegate !is null) {
344             //writeln("Lazy! opCall() Delegate is set! Calling it to get value");
345             T[] value = cast(T[])_delegate();
346             //writeln("Lazy! opCall() delegate returned value " ~ value.classinfo.toString);
347             opAssign(value);
348         } else {
349             //writeln("Lazy! opCall() Returning value instantly");
350         }
351         return _value;
352     }
353     
354     TT opCast(TT)() if (isArray!TT && isImplicitlyConvertible!(typeof(TT.init[0]), Object)) {
355         return cast(TT)get();
356     }
357     
358     T[] opAssign(T[] v) {
359         //writeln("Lazy! opAssign(value)");
360         _value = v;
361         _delegate = null;
362         return _value;
363     }
364     
365     ref LazyCollection!T opAssign(ref LazyCollection!T v) {
366         //writeln("Lazy! opAssign(value)");
367         _value = v._value;
368         _delegate = v._delegate;
369         return this;
370     }
371     
372     void opAssign(delegate_t lazyLoader) {
373         //writeln("Lazy! opAssign(delegate)");
374         _delegate = lazyLoader;
375         _value = null;
376     }
377     
378     alias get this;
379 }
380 
381 unittest {
382 
383     class Foo {
384         string name;
385         this(string v) {
386             name = v;
387         }
388     }
389     struct LazyVar(T) {
390         T a;
391         int b;
392         T opCall() {
393             b++;
394             return a;
395         }
396         alias opCall this;
397     }
398     class TestLazy {
399         LazyVar!Foo l;
400     }
401 
402     auto loader = delegate() {
403         return new Foo("lazy loaded");
404     };
405 
406     Foo res;
407     Lazy!Foo field;
408     res = field();
409     assert(res is null);
410     field = loader;
411     res = field();
412     assert(res.name == "lazy loaded");
413     field = new Foo("another string");
414     res = cast(Foo)field;
415     assert(res.name == "another string");
416 
417     static class Bar {
418         @property Lazy!Foo field;
419     }
420     Bar bar = new Bar();
421     bar.field = new Foo("name1");
422     res = bar.field();
423 
424     LazyVar!string s;
425     s.a = "10";
426     assert(s() == "10");
427     assert(s.b == 1);
428     s.a = "15";
429     assert(s == "15");
430     assert(s.b == 2);
431 
432     import dstruct.metadata;
433     TestLazy tl = new TestLazy();
434 }
435