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