1 /// 2 module dpq2.conv.from_d_types; 3 4 @safe: 5 6 public import dpq2.conv.arrays : isArrayType, toValue; 7 public import dpq2.conv.geometric : toValue; 8 import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC; 9 import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType; 10 import dpq2.value : Value, ValueFormat; 11 12 import std.bitmanip: nativeToBigEndian; 13 import std.datetime.date: Date, DateTime, TimeOfDay; 14 import std.datetime.systime: SysTime; 15 import std.datetime.timezone: LocalTime, TimeZone, UTC; 16 import std.traits: isImplicitlyConvertible, isNumeric, isInstanceOf, OriginalType, Unqual; 17 import std.typecons : Nullable; 18 import std.uuid: UUID; 19 import vibe.data.json: Json; 20 import money: currency; 21 22 /// Converts Nullable!T to Value 23 Value toValue(T)(T v) 24 if (is(T == Nullable!R, R) && !(isArrayType!(typeof(v.get)))) 25 { 26 if (v.isNull) 27 return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); 28 else 29 return toValue(v.get); 30 } 31 32 /// ditto 33 Value toValue(T)(T v) 34 if (is(T == Nullable!R, R) && (isArrayType!(typeof(v.get)))) 35 { 36 import dpq2.conv.arrays : arrToValue = toValue; // deprecation import workaround 37 import std.range : ElementType; 38 39 if (v.isNull) 40 return Value(ValueFormat.BINARY, detectOidTypeFromNative!(ElementType!(typeof(v.get))).oidConvTo!"array"); 41 else 42 return arrToValue(v.get); 43 } 44 45 /// 46 Value toValue(T)(T v) 47 if(isNumeric!(T)) 48 { 49 return Value(v.nativeToBigEndian.dup, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 50 } 51 52 /// Convert money.currency to PG value 53 /// 54 /// Caution: here is no check of fractional precision while conversion! 55 /// See also: PostgreSQL's "lc_monetary" description and "money" package description 56 Value toValue(T)(T v) 57 if(isInstanceOf!(currency, T) && T.amount.sizeof == 8) 58 { 59 return Value(v.amount.nativeToBigEndian.dup, OidType.Money, false, ValueFormat.BINARY); 60 } 61 62 unittest 63 { 64 import dpq2.conv.to_d_types: PGTestMoney; 65 66 const pgtm = PGTestMoney(-123.45); 67 68 Value v = pgtm.toValue; 69 70 assert(v.oidType == OidType.Money); 71 assert(v.as!PGTestMoney == pgtm); 72 } 73 74 /** 75 Converts types implicitly convertible to string to PG Value. 76 Note that if string is null it is written as an empty string. 77 If NULL is a desired DB value, Nullable!string can be used instead. 78 */ 79 Value toValue(T)(T v, ValueFormat valueFormat = ValueFormat.BINARY) @trusted 80 if(is(T : string)) 81 { 82 import std..string : representation; 83 84 static assert(isImplicitlyConvertible!(T, string)); 85 auto buf = (cast(string) v).representation; 86 87 if(valueFormat == ValueFormat.TEXT) buf ~= 0; // for prepareArgs only 88 89 return Value(buf, OidType.Text, false, valueFormat); 90 } 91 92 /// Constructs Value from array of bytes 93 Value toValue(T)(T v) 94 if(is(T : immutable(ubyte)[])) 95 { 96 return Value(v, detectOidTypeFromNative!(ubyte[]), false, ValueFormat.BINARY); 97 } 98 99 /// Constructs Value from boolean 100 Value toValue(T : bool)(T v) @trusted 101 if (!is(T == Nullable!R, R)) 102 { 103 immutable ubyte[] buf = [ v ? 1 : 0 ]; 104 105 return Value(buf, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 106 } 107 108 /// Constructs Value from Date 109 Value toValue(T)(T v) 110 if (is(Unqual!T == Date)) 111 { 112 import std.conv: to; 113 import dpq2.value; 114 import dpq2.conv.time: POSTGRES_EPOCH_JDATE; 115 116 long mj_day = v.modJulianDay; 117 118 // max days isn't checked because Phobos Date days value always fits into Postgres Date 119 if (mj_day < -POSTGRES_EPOCH_JDATE) 120 throw new ValueConvException( 121 ConvExceptionType.DATE_VALUE_OVERFLOW, 122 "Date value doesn't fit into Postgres binary Date", 123 __FILE__, __LINE__ 124 ); 125 126 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 127 long days = mj_day - mj_pg_epoch; 128 129 return Value(nativeToBigEndian(days.to!int).dup, OidType.Date, false); 130 } 131 132 /// Constructs Value from TimeOfDay 133 Value toValue(T)(T v) 134 if (is(Unqual!T == TimeOfDay)) 135 { 136 long us = ((60L * v.hour + v.minute) * 60 + v.second) * 1_000_000; 137 138 return Value(nativeToBigEndian(us).dup, OidType.Time, false); 139 } 140 141 /// Constructs Value from TimeStamp or from TimeStampUTC 142 Value toValue(T)(T v) 143 if (is(Unqual!T == TimeStamp) || is(Unqual!T == TimeStampUTC)) 144 { 145 long us; /// microseconds 146 147 if(v.isLater) // infinity 148 us = us.max; 149 else if(v.isEarlier) // -infinity 150 us = us.min; 151 else 152 { 153 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 154 long j = modJulianDayForIntYear(v.date.year, v.date.month, v.date.day) - mj_pg_epoch; 155 us = (((j * 24 + v.time.hour) * 60 + v.time.minute) * 60 + v.time.second) * 1_000_000 + v.fracSec.total!"usecs"; 156 } 157 158 return Value( 159 nativeToBigEndian(us).dup, 160 is(Unqual!T == TimeStamp) ? OidType.TimeStamp : OidType.TimeStampWithZone, 161 false 162 ); 163 } 164 165 private auto modJulianDayForIntYear(const int year, const ubyte month, const short day) pure 166 { 167 // Wikipedia magic: 168 169 const a = (14 - month) / 12; 170 const y = year + 4800 - a; 171 const m = month + a * 12 - 3; 172 173 const jd = day + (m*153+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045; 174 175 return jd - 2_400_001; 176 } 177 unittest 178 { 179 assert(modJulianDayForIntYear(1858, 11, 17) == 0); 180 assert(modJulianDayForIntYear(2010, 8, 24) == 55_432); 181 assert(modJulianDayForIntYear(1999, 7, 6) == 51_365); 182 } 183 184 /++ 185 Constructs Value from DateTime 186 It uses Timestamp without TZ as a resulting PG type 187 +/ 188 Value toValue(T)(T v) 189 if (is(Unqual!T == DateTime)) 190 { 191 return TimeStamp(v).toValue; 192 } 193 194 /++ 195 Constructs Value from SysTime 196 Note that SysTime has a precision in hnsecs and PG TimeStamp in usecs. 197 It means that PG value will have 10 times lower precision. 198 And as both types are using long for internal storage it also means that PG TimeStamp can store greater range of values than SysTime. 199 +/ 200 Value toValue(T)(T v) 201 if (is(Unqual!T == SysTime)) 202 { 203 long us = (v - SysTime(POSTGRES_EPOCH_DATE, UTC())).total!"usecs"; 204 205 return Value(nativeToBigEndian(us).dup, OidType.TimeStampWithZone, false); 206 } 207 208 /// Constructs Value from UUID 209 Value toValue(T)(T v) 210 if (is(Unqual!T == UUID)) 211 { 212 return Value(v.data.dup, OidType.UUID); 213 } 214 215 /// Constructs Value from Json 216 Value toValue(T)(T v) 217 if (is(Unqual!T == Json)) 218 { 219 auto r = toValue(v.toString); 220 r.oidType = OidType.Json; 221 222 return r; 223 } 224 225 version(unittest) 226 import dpq2.conv.to_d_types : as; 227 228 unittest 229 { 230 Value v = toValue(cast(short) 123); 231 232 assert(v.oidType == OidType.Int2); 233 assert(v.as!short == 123); 234 } 235 236 unittest 237 { 238 Value v = toValue(-123.456); 239 240 assert(v.oidType == OidType.Float8); 241 assert(v.as!double == -123.456); 242 } 243 244 unittest 245 { 246 Value v = toValue("Test string"); 247 248 assert(v.oidType == OidType.Text); 249 assert(v.as!string == "Test string"); 250 } 251 252 // string Null values 253 @system unittest 254 { 255 { 256 import core.exception: AssertError; 257 import std.exception: assertThrown; 258 259 auto v = Nullable!string.init.toValue; 260 assert(v.oidType == OidType.Text); 261 assert(v.isNull); 262 263 assertThrown!AssertError(v.as!string); 264 assert(v.as!(Nullable!string).isNull); 265 } 266 267 { 268 string s; 269 auto v = s.toValue; 270 assert(v.oidType == OidType.Text); 271 assert(!v.isNull); 272 } 273 } 274 275 unittest 276 { 277 immutable ubyte[] buf = [0, 1, 2, 3, 4, 5]; 278 Value v = toValue(buf); 279 280 assert(v.oidType == OidType.ByteArray); 281 assert(v.as!(const ubyte[]) == buf); 282 } 283 284 unittest 285 { 286 Value t = toValue(true); 287 Value f = toValue(false); 288 289 assert(t.as!bool == true); 290 assert(f.as!bool == false); 291 } 292 293 unittest 294 { 295 Value v = toValue(Nullable!long(1)); 296 Value nv = toValue(Nullable!bool.init); 297 298 assert(!v.isNull); 299 assert(v.oidType == OidType.Int8); 300 assert(v.as!long == 1); 301 302 assert(nv.isNull); 303 assert(nv.oidType == OidType.Bool); 304 } 305 306 unittest 307 { 308 import std.datetime : DateTime; 309 310 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 311 312 assert(!v.isNull); 313 assert(v.oidType == OidType.TimeStamp); 314 } 315 316 unittest 317 { 318 // Date: '2018-1-15' 319 auto d = Date(2018, 1, 15); 320 auto v = toValue(d); 321 322 assert(v.oidType == OidType.Date); 323 assert(v.as!Date == d); 324 } 325 326 unittest 327 { 328 auto d = immutable Date(2018, 1, 15); 329 auto v = toValue(d); 330 331 assert(v.oidType == OidType.Date); 332 assert(v.as!Date == d); 333 } 334 335 unittest 336 { 337 // Date: '2000-1-1' 338 auto d = Date(2000, 1, 1); 339 auto v = toValue(d); 340 341 assert(v.oidType == OidType.Date); 342 assert(v.as!Date == d); 343 } 344 345 unittest 346 { 347 // Date: '0010-2-20' 348 auto d = Date(10, 2, 20); 349 auto v = toValue(d); 350 351 assert(v.oidType == OidType.Date); 352 assert(v.as!Date == d); 353 } 354 355 unittest 356 { 357 // Date: max (always fits into Postgres Date) 358 auto d = Date.max; 359 auto v = toValue(d); 360 361 assert(v.oidType == OidType.Date); 362 assert(v.as!Date == d); 363 } 364 365 unittest 366 { 367 // Date: min (overflow) 368 import std.exception: assertThrown; 369 import dpq2.value: ValueConvException; 370 371 auto d = Date.min; 372 assertThrown!ValueConvException(d.toValue); 373 } 374 375 unittest 376 { 377 // DateTime 378 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 379 auto v = toValue(d); 380 381 assert(v.oidType == OidType.TimeStamp); 382 assert(v.as!DateTime == d); 383 } 384 385 unittest 386 { 387 // Nullable!DateTime 388 import std.typecons : nullable; 389 auto d = nullable(DateTime(2018, 2, 20, 1, 2, 3)); 390 auto v = toValue(d); 391 392 assert(v.oidType == OidType.TimeStamp); 393 assert(v.as!(Nullable!DateTime) == d); 394 395 d.nullify(); 396 v = toValue(d); 397 assert(v.oidType == OidType.TimeStamp); 398 assert(v.as!(Nullable!DateTime).isNull); 399 } 400 401 unittest 402 { 403 // TimeOfDay: '14:29:17' 404 auto tod = TimeOfDay(14, 29, 17); 405 auto v = toValue(tod); 406 407 assert(v.oidType == OidType.Time); 408 assert(v.as!TimeOfDay == tod); 409 } 410 411 unittest 412 { 413 // SysTime: '2017-11-13T14:29:17.075678Z' 414 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 415 auto v = toValue(t); 416 417 assert(v.oidType == OidType.TimeStampWithZone); 418 assert(v.as!SysTime == t); 419 } 420 421 unittest 422 { 423 import core.time : usecs; 424 import std.datetime.date : DateTime; 425 426 // TimeStamp: '2017-11-13 14:29:17.075678' 427 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 428 auto v = toValue(t); 429 430 assert(v.oidType == OidType.TimeStamp); 431 assert(v.as!TimeStamp == t); 432 } 433 434 unittest 435 { 436 auto j = Json(["foo":Json("bar")]); 437 auto v = j.toValue; 438 439 assert(v.oidType == OidType.Json); 440 assert(v.as!Json == j); 441 442 auto nj = Nullable!Json(j); 443 auto nv = nj.toValue; 444 assert(nv.oidType == OidType.Json); 445 assert(!nv.as!(Nullable!Json).isNull); 446 assert(nv.as!(Nullable!Json).get == j); 447 }