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